﻿using System.Windows;
using System.Windows.Input;
using System.IO;
using System.Collections.Generic;
using Microsoft.Win32;
using System.Text;
using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml;
using System.Windows.Markup;
using MahApps.Metro.Controls;
using System.Windows.Controls.Primitives;
using ICSharpCode.AvalonEdit.Document;
using System.Media;
using System.Text.RegularExpressions;
using System.Windows.Shell;
using mshtml;
using System.Net;
using Newtonsoft.Json;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
using Microsoft.WindowsAPICodePack.Dialogs;
using System.Linq;
using System.Windows.Data;
using System.Reflection;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : MetroWindow
    {
        /// <summary>
        /// Html Help Workshop安装路径。
        /// </summary>
        private string hhwInstalledPath = Globals.hhwInstalledPath;
        /// <summary>
        /// Html Help Workshop安装路径。
        /// </summary>
        public string HHWInstalledPath { get { return this.hhwInstalledPath; } }

        /// <summary>
        /// Html Help Compiler(hhc.exe)安装路径。
        /// </summary>
        private string hhcInstalledPath = Globals.hhcInstalledPath;
        /// <summary>
        /// Html Help Compiler(hhc.exe)安装路径。
        /// </summary>
        public string HHCInstalledPath { get { return this.hhcInstalledPath; } }

        private ImageSource previewAppImageSource;
        /// <summary>
        /// 应用程序图标的放大版，用于默认情况下图像预览区预览图像。
        /// </summary>
        public ImageSource PreviewAppImageSource
        {
            get { return previewAppImageSource; }
        }

        #region 解决 PopUp 位置偏移的问题

        private static readonly FieldInfo _menuDropAlignmentField;
        static MainWindow()
        {
            _menuDropAlignmentField = typeof(SystemParameters).GetField("_menuDropAlignment", BindingFlags.NonPublic | BindingFlags.Static);
            System.Diagnostics.Debug.Assert(_menuDropAlignmentField != null);

            EnsureStandardPopupAlignment();
            SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
        }

        private static void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            EnsureStandardPopupAlignment();
        }

        private static void EnsureStandardPopupAlignment()
        {
            if (SystemParameters.MenuDropAlignment && _menuDropAlignmentField != null)
            {
                _menuDropAlignmentField.SetValue(null, false);
            }
        }

        #endregion

        /// <summary>
        /// [构造方法]，初始化主窗口。
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // 解决 Win 10 下 PopUp 位置偏移问题。
            FixPopupBug();

            previewAppImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/App.png"));

            tcManagerPanels.IsItemListButtonVisible = false;
            tcRightToolBar.IsItemListButtonVisible = false;

            #region 用来解决主选项卡右上角按钮弹出各文件列表功能
            //mainTabControl.ItemsListBox.BorderThickness = new Thickness(1, 0, 1, 0);
            //mainTabControl.ItemsListBox.BorderBrush = Brushes.DarkGoldenrod;
            mainTabControl.IsItemListButtonVisible = true;
            mainTabControl.ItemListButtonClicked += MainTabControl_ItemListButtonClicked;
            #endregion

            try
            {
                this.workspaceManager = new WorkspaceManager(this);

                //须移动到Loaded事件末尾，否则在工作区管理器中的条目尚未载入的情况下易出错。
                //this.mainTabControl.SelectionChanged += mainTabControl_SelectionChanged;
                this.Closing += MainWindow_Closing;
                this.Loaded += MainWindow_Loaded;

                //提供托盘图标
                CreateNoticeIcon();

                //保证窗体显示在上方。
                windowState = WindowState;

                helpFrame.Source = new Uri(Globals.PathOfWorkspace + "Help~.html");//不能加这个前缀"file:///" +

                #region 载入之前指定的各个历史工作区目录
                if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    string[] workspaces = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                    if (workspaces.Length > 0)
                    {
                        for (int i = 0; i < workspaces.Length; i++)
                        {
                            if (string.IsNullOrEmpty(workspaces[i]) == false)
                            {
                                var rw = new RecentDirectoryListBoxItem(workspaces[i])
                                {
                                    ToolTip = "双击设置为当前工作区目录",
                                };
                                rw.MouseDoubleClick += rw_MouseDoubleClick;
                                lbxHistoryWorkspaces.Items.Add(rw);
                            }
                        }
                    }
                }

                RefreshWorkspaceHistoryList();
                #endregion

                #region 载入之前指定的各个历史导出目录
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    string[] outports = File.ReadAllLines(Globals.PathOfHistoryOutputFileFullName);
                    if (outports.Length > 0)
                    {
                        for (int i = 0; i < outports.Length; i++)
                        {
                            if (string.IsNullOrEmpty(outports[i]) == false)
                            {
                                var row = new RecentDirectoryListBoxItem(outports[i])
                                {
                                    ToolTip = "双击将编译的所有 Html 文件及资源文件导出到该目录",
                                };
                                row.MouseDoubleClick += row_MouseDoubleClick;
                                lbxHistoryOutport.Items.Add(row);
                            }
                        }
                    }
                }

                RefreshOutputHistoryList();
                #endregion

                #region 快捷键事件处理

                //注意，某些快捷键是全局的，因此可以放在主窗口事件中处理；
                //例如增加字号、减小字号、保存文件、全部保存、新建文件、打开文件、关闭窗口（不需要处理）、生成HTML、生成填空文本、帮助文档……
                //而另一些快捷键必须放到TabControl的事件中处理。
                //这些命令使用的快捷键与Explorer使用的快捷键会产生冲突，此时必须在TabControl中实现。
                //包括：Ctrl+X,Ctrl+C,Ctrl+V,Ctrl+A,Ctrl+Z,Ctrl+Y,Del

                //*** 不清楚是不是会产生冲突的快捷键，为防止误操作，一律在主窗口的键盘事件中进行处理

                this.PreviewKeyDown += MainWindow_PreviewKeyDown;
                mainTabControl.PreviewKeyDown += mainTabControl_PreviewKeyDown;

                #endregion

                //treeview items 变色
                //已无必要存在，因为使用了 MetroTreeViewItem 样式后即使失焦也可以保留当前选择的项。
                //tvWorkDirectory.SelectedItemChanged += TvWorkDirectory_SelectedItemChanged;
                //tvFindAndReplace.SelectedItemChanged += TvFind_SelectedItemChanged;
                //tvTaskList.SelectedItemChanged += TvFind_SelectedItemChanged;

                //载入用户偏好设置项目
                LoadUserPreference();

                // 刷新查找、替换按钮的可用状态
                RefreshFindButtonsStatus();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }

            WorkspaceChanging += MainWindow_WorkspaceChanging;
            WorkspaceChanged += MainWindow_WorkspaceChanged;
            this.HtmlOptionChanged += MainWindow_HtmlOptionChanged;
        }

        private int openAndModifiedWorkspacePythonScripts = 0;

        private void MainWindow_WorkspaceChanging(object sender, WorkspaceChangedEventArgs e)
        {
            openAndModifiedWorkspacePythonScripts = 0;
            foreach (var item in lbxWorkspacePythonScripts.Items)
            {
                var psli = item as PythonScriptListItem;
                if (psli == null || psli.Location != PythonScriptLocation.Workspace || psli.Editor == null) continue;
                if (psli.Editor.editorBase.IsModified == false)
                {
                    psli.Editor.Close();
                }
                else
                {
                    openAndModifiedWorkspacePythonScripts++;
                }
            }
        }

        private void MainWindow_HtmlOptionChanged(object sender, HtmlCompileChangedEventArgs e)
        {
            if (AlwaysCompileBeforePreview == false)
            {
                var r = LMessageBox.Show($"对【{e.HtmlOptionText}】的新设置不会立刻起作用！\r\n\r\n" +
                    $"　　这是因为〖预览前总是编译〗选项处于关闭状态，现在执行预览操作看到的仍然是之前编译的旧 Html 文件！\r\n\r\n" +
                    $"　　建议开启〖预览前总是编译〗选项再预览，如果不需要保留旧 Html 文件也可以重新编译整个工作区。\r\n\r\n" +
                    $"　　〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\r\n" +
                    $"　　要自动开启〖预览前总是编译〗选项吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (r == MessageBoxResult.Yes)
                    MiAlwaysCompileBeforePreview_Click(sender, null);
                return;
            }
        }

        private WebSearchWindow searchWindow = null;
        /// <summary>
        /// 搜索框。
        /// </summary>
        public WebSearchWindow SearchWindow
        {
            get
            {
                if (searchWindow == null)
                {
                    searchWindow = new WebSearchWindow() { Owner = this, };
                }
                return searchWindow;
            }
        }

        private void MainWindow_WorkspaceChanged(object sender, WorkspaceChangedEventArgs e)
        {
            var title = GetMetaFileTitleOfDirectory(e.NewWorkspaceDirectoryFullPath);
            if (string.IsNullOrWhiteSpace(title))
            {
                DirectoryInfo di = new DirectoryInfo(e.NewWorkspaceDirectoryFullPath);
                if (di != null)
                {
                    this.Title = di.Name + $" - {Globals.AppName}";
                }
                else this.Title = Globals.AppName;
            }
            else
            {
                this.Title = title + $" - {Globals.AppName}";
            }

            this.notifyIcon.Text = this.Title;
            LoadWorkspaceEnvironmentValues();

            tvFindAndReplace.Items.Clear();
            tvTaskList.Items.Clear();

            AnchorsManager.Load();

            // 刷新工作区文件夹图标
            WorkspaceFolderIcon.RefreshWorkspaceFolderIcon();

            // 载入工作区脚本列表
            LoadWorkspacePythonScriptFiles();
        }

        /// <summary>
        /// 清理任务栏上的JumpList
        /// </summary>
        private void ClearJumpList()
        {
            JumpList jumpList1 = JumpList.GetJumpList(App.Current);
            jumpList1.JumpItems.Clear();
            jumpList1.Apply();
        }

        /// <summary>
        /// 左工具栏的竖栏切换功能。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tcManagerPanels_ItemListButtonClicked(object sender, MouseButtonEventArgs e)
        {
            if (tcManagerPanels.ItemsScrollViewer.Visibility != Visibility.Collapsed)
            {
                tcManagerPanels.ItemsScrollViewer.Visibility = Visibility.Collapsed;
                return;
            }

            tcManagerPanels.ItemsStackPanel.Style = null;
            tcManagerPanels.ItemsScrollViewer.Background = Brushes.White;
            tcManagerPanels.ItemsScrollViewer.Width = mainTabControl.ActualWidth / 2;
            tcManagerPanels.ItemsScrollViewer.MaxWidth = 340;
            tcManagerPanels.ItemsScrollViewer.Visibility = Visibility.Visible;

            tcManagerPanels.ItemsStackPanel.Children.Clear();

            var squareButtonStyle = FindResource("LSquareButtonStyle") as Style;

            foreach (var item in tcManagerPanels.Items)
            {
                if (!(item is TabItem ti)) continue;
                if (ti.Visibility != Visibility.Visible) continue;   //文档不存在隐藏的问题，所以没有这行。

                //StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, };
                StackPanel spHeader = new StackPanel() { Orientation = Orientation.Horizontal, };
                TextBlock tbHeader = new TextBlock()
                {
                    Text = ti.Tag.ToString(),
                    Margin = new Thickness(4, 2, 4, 2),
                    Width = 320,
                };

                spHeader.Children.Add(tbHeader);

                var li = new Button()
                {
                    Style = squareButtonStyle,
                    FontWeight = FontWeights.Normal,
                    FontSize = 14,
                    BorderThickness = new Thickness(0, 0, 0, 1),
                    Tag = ti,
                    Content = spHeader,
                    Margin = new Thickness(0, 4, 0, 4),
                    ToolTip = "点击切换到对应选项卡",
                };
                li.Click += LiLeftTools_Click;

                tcManagerPanels.ItemsStackPanel.Children.Add(li);
            }
        }

        private void LiLeftTools_Click(object sender, RoutedEventArgs e)
        {
            tcManagerPanels.ItemsStackPanel.Children.Clear();
            tcManagerPanels.ItemsScrollViewer.Visibility = Visibility.Collapsed;

            tcManagerPanels.SelectedItem = (sender as Button).Tag as TabItem;
        }

        private void MainTabControl_ItemListButtonClicked(object sender, MouseButtonEventArgs e)
        {
            if (mainTabControl.ItemsScrollViewer.Visibility != Visibility.Collapsed)
            {
                mainTabControl.ItemsScrollViewer.Visibility = Visibility.Collapsed;
                return;
            }

            //VirtualizingStackPanel.SetIsVirtualizing(this.mainTabControl.ItemsListBox, true);
            //VirtualizingStackPanel.SetVirtualizationMode(this.mainTabControl.ItemsListBox, VirtualizationMode.Recycling);
            //ScrollViewer.SetCanContentScroll(this.mainTabControl.ItemsListBox, false);

            mainTabControl.ItemsStackPanel.Style = null;
            mainTabControl.ItemsScrollViewer.Background = Brushes.White;
            mainTabControl.ItemsScrollViewer.Width = mainTabControl.ActualWidth / 2;
            mainTabControl.ItemsScrollViewer.MaxWidth = 340;
            mainTabControl.ItemsScrollViewer.Visibility = Visibility.Visible;

            mainTabControl.ItemsStackPanel.Children.Clear();

            var squareButtonStyle = FindResource("LSquareButtonStyle") as Style;

            foreach (var item in mainTabControl.Items)
            {
                if (item == mainTabControl.SelectedItem) continue;
                if (!(item is MarkdownEditor mde)) continue;

                //StackPanel sp = new StackPanel() { Orientation = Orientation.Vertical, };
                StackPanel spHeader = new StackPanel() { Orientation = Orientation.Horizontal, };
                TextBlock tbHeader = new TextBlock()
                {
                    Text = mde.Title,
                    Margin = new Thickness(4, 2, 4, 2),
                    Width = 320,
                };
                //Image imgheader = new Image() { Width = 16, Height = 16, };
                //imgheader.Source = mde.IconSource;
                //spHeader.Children.Add(imgheader);
                spHeader.Children.Add(tbHeader);

                //TextBlock tbComment = new TextBlock()
                //{
                //    Text = ":> " + mde.FullFilePath,
                //    Margin = new Thickness(4, 2, 4, 2),
                //    Width = 320,
                //    Foreground = Brushes.DarkGoldenrod,
                //    TextWrapping = TextWrapping.WrapWithOverflow,
                //};
                //sp.Children.Add(spHeader);
                //sp.Children.Add(tbComment);

                //var border = new Border()
                //{
                //    Child = sp,
                //    BorderBrush = Brushes.DarkGoldenrod,
                //    BorderThickness = new Thickness(0, 0, 0, 1),
                //    SnapsToDevicePixels = true,
                //};

                var li = new Button()
                {
                    Style = squareButtonStyle,
                    FontWeight = FontWeights.Normal,
                    FontSize = 14,
                    BorderThickness = new Thickness(0, 0, 0, 1),
                    Tag = mde,
                    Content = spHeader,
                    Margin = new Thickness(0, 4, 0, 4),
                    ToolTip = "点击切换到对应文档",
                };
                li.Click += LiDocument_Click;

                mainTabControl.ItemsStackPanel.Children.Add(li);
            }
        }

        private void LiDocument_Click(object sender, RoutedEventArgs e)
        {
            mainTabControl.ItemsStackPanel.Children.Clear();
            mainTabControl.ItemsScrollViewer.Visibility = Visibility.Collapsed;

            mainTabControl.SelectedItem = (sender as Button).Tag as MarkdownEditor;
        }

        //private void TvFind_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        //{
        //    //已无必要存在，因为使用了 MetroTreeViewItem 样式后即使失焦也可以保留当前选择的项。
        //    //IFindItem oldItem = (e.OldValue as IFindItem);
        //    //if (oldItem != null)
        //    //{
        //    //    oldItem.HeaderTextBlock.Foreground = oldItem.ForegroundOfText;
        //    //    oldItem.BorderBrush = Brushes.Transparent;
        //    //}

        //    //var newItem = (e.NewValue as IFindItem);
        //    //if (newItem != null)
        //    //{
        //    //    newItem.HeaderTextBlock.Foreground = Brushes.Black;
        //    //    newItem.BorderBrush = WorkspaceTreeViewItem.TreeViewItemBorderBrush;
        //    //}
        //}

        /// <summary>
        /// 工作区管理器中各条目变色功能。
        /// </summary>
        //private void TvWorkDirectory_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        //{
        //    //已无必要存在，因为使用了 MetroTreeViewItem 样式后即使失焦也可以保留当前选择的项。
        //    //var oldItem = (e.OldValue as WorkspaceTreeViewItem);
        //    //if (oldItem != null)
        //    //{
        //    //    oldItem.HeaderTextBlock.Foreground = oldItem.ForegroundOfText;
        //    //    oldItem.BorderBrush = Brushes.Transparent;
        //    //}

        //    //var newItem = (e.NewValue as WorkspaceTreeViewItem);
        //    //if (newItem != null)
        //    //{
        //    //    newItem.HeaderTextBlock.Foreground = Brushes.Black;
        //    //    newItem.BorderBrush = WorkspaceTreeViewItem.TreeViewItemBorderBrush;
        //    //}
        //}

        #region 字体相关

        private FontFamily defaultFontFamily;

        public FontFamily DefaultFontFamily
        {
            get { return defaultFontFamily; }
            set { defaultFontFamily = value; }
        }

        private bool _familyListValid;
        private ICollection<FontFamily> _familyCollection;

        public ICollection<FontFamily> FontFamilyCollection
        {
            get
            {
                return (_familyCollection == null) ? Fonts.SystemFontFamilies : _familyCollection;
            }

            set
            {
                if (value != _familyCollection)
                {
                    _familyCollection = value;
                    InvalidateFontFamilyList();
                }
            }
        }

        private void InvalidateFontFamilyList()
        {
            if (_familyListValid)
            {
                fontFamilyList.Items.Clear();
                _familyListValid = false;
            }
        }

        private void InitializeFontFamilyList()
        {
            ICollection<FontFamily> familyCollection = FontFamilyCollection;
            if (familyCollection != null)
            {
                FontFamilyListItem[] items = new FontFamilyListItem[familyCollection.Count];

                int i = 0;

                foreach (FontFamily family in familyCollection)
                {
                    items[i++] = new FontFamilyListItem(family);
                }

                Array.Sort<FontFamilyListItem>(items);

                foreach (FontFamilyListItem item in items)
                {
                    fontFamilyList.Items.Add(new ComboBoxItem() { Content = item, });
                }
            }
        }

        #endregion

        /// <summary>
        /// 双击“最近工作区列表”中的某个条目，将工作区切换到该条目记录的目录。
        /// </summary>
        void rw_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ChangeWorkspace((sender as RecentDirectoryListBoxItem).DirectoryPath);
        }

        /// <summary>
        /// 复制特定图像资源文件到指定目录中去。
        /// </summary>
        /// <param name="destImageFolder">目标位置。</param>
        /// <param name="fileShortName">图像文件短名。</param>
        internal static void CopyImageFile(string destImageFolder, string fileShortName)
        {
            var destIamgeFilePath = destImageFolder + fileShortName;
            var srcImageFilePath = Globals.DefaultWorkspacePath + $"Images~\\{fileShortName}";
            if (File.Exists(srcImageFilePath))
            {
                if (File.Exists(destIamgeFilePath))
                {
                    FileInfo srcIamgeFileInfo = new FileInfo(srcImageFilePath);
                    FileInfo destImageFileInfo = new FileInfo(destIamgeFilePath);
                    if (destImageFileInfo.LastWriteTime.CompareTo(srcIamgeFileInfo.LastWriteTime) < 0)
                    {
                        File.Copy(srcImageFilePath, destIamgeFilePath, true);
                    }
                }
                else
                {
                    File.Copy(srcImageFilePath, destIamgeFilePath, true);
                }
            }
        }

        /// <summary>
        /// 将编译后的 Html 文件需要的资源文件复制到目标工作区目录中去。
        /// </summary>
        /// <param name="workspace">目标工作区目录路径。</param>
        private static void CopyCssAndResourceFiles(string workspace)
        {
            if (string.IsNullOrEmpty(workspace)) return;

            if (workspace.EndsWith("\\") == false) workspace += "\\";

            if (workspace == Globals.DefaultWorkspacePath) return;//不需要复制。

            if (Directory.Exists(workspace) == false)
            {
                Directory.CreateDirectory(workspace);
            }

            var destImageFolder = workspace + "Images~\\";
            if (Directory.Exists(destImageFolder) == false)
            {
                Directory.CreateDirectory(destImageFolder);
            }

            try
            {
                CopyImageFile(destImageFolder, "header_icon_dark.png");
                CopyImageFile(destImageFolder, "header_icon_light.png");
                CopyImageFile(destImageFolder, "comment_dark.png");
                CopyImageFile(destImageFolder, "comment_light.png");

                CopyImageFile(destImageFolder, "region_e_dark.png");
                CopyImageFile(destImageFolder, "region_e_light.png");
                CopyImageFile(destImageFolder, "region_i_dark.png");
                CopyImageFile(destImageFolder, "region_i_light.png");
                CopyImageFile(destImageFolder, "region_q_dark.png");
                CopyImageFile(destImageFolder, "region_q_light.png");
                CopyImageFile(destImageFolder, "region_w_dark.png");
                CopyImageFile(destImageFolder, "region_w_light.png");

                CopyImageFile(destImageFolder, "menu_light.png");
                CopyImageFile(destImageFolder, "menu_dark.png");

                CopyImageFile(destImageFolder, "theme_switcher.png");

                //jQuery 2.0以上版本对IE的版本有要求，不适合本程序使用。
                var destJQuery = workspace + "jquery-1.7.0.min.js";
                if (File.Exists(destJQuery) == false)
                {
                    File.Copy(Globals.DefaultWorkspacePath + "jquery-1.7.0.min.js", destJQuery);
                }

                var destLessonDarkCSS = workspace + "lesson_dark.css";
                var srcLessonDarkCSS = Globals.DefaultWorkspacePath + "lesson_dark.css";
                if (File.Exists(srcLessonDarkCSS))
                {
                    if (File.Exists(destLessonDarkCSS))
                    {
                        FileInfo srcLessonDarkCSSFileInfo = new FileInfo(srcLessonDarkCSS);
                        FileInfo destLessonDarkCSSFileInfo = new FileInfo(destLessonDarkCSS);
                        if (destLessonDarkCSSFileInfo.LastWriteTime.CompareTo(srcLessonDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcLessonDarkCSS, destLessonDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcLessonDarkCSS, destLessonDarkCSS, true);
                    }
                }

                var destLessonLightCSS = workspace + "lesson_light.css";
                var srcLessonLightCSS = Globals.DefaultWorkspacePath + "lesson_light.css";
                if (File.Exists(srcLessonLightCSS))
                {
                    if (File.Exists(destLessonLightCSS))
                    {
                        FileInfo destLessonLightCSSFileInfo = new FileInfo(destLessonLightCSS);
                        FileInfo srcLessonLightCSSFileInfo = new FileInfo(srcLessonLightCSS);
                        if (destLessonLightCSSFileInfo.LastWriteTime.CompareTo(srcLessonLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcLessonLightCSS, destLessonLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcLessonLightCSS, destLessonLightCSS, true);
                    }
                }

                var destMenuLightCSS = workspace + "menu_light.css";
                var srcMenuLightCSS = Globals.DefaultWorkspacePath + "menu_light.css";
                if (File.Exists(srcMenuLightCSS))
                {
                    if (File.Exists(destMenuLightCSS))
                    {
                        FileInfo destMenuLightCSSFileInfo = new FileInfo(destMenuLightCSS);
                        FileInfo srcMenuLightCSSFileInfo = new FileInfo(srcMenuLightCSS);
                        if (destMenuLightCSSFileInfo.LastWriteTime.CompareTo(srcMenuLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuLightCSS, destMenuLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuLightCSS, destMenuLightCSS, true);
                    }
                }

                var destMenuDarkCSS = workspace + "menu_dark.css";
                var srcMenuDarkCSS = Globals.DefaultWorkspacePath + "menu_dark.css";
                if (File.Exists(srcMenuDarkCSS))
                {
                    if (File.Exists(destMenuDarkCSS))
                    {
                        FileInfo destMenuDarkCSSFileInfo = new FileInfo(destMenuDarkCSS);
                        FileInfo srcMenuDarkCSSFileInfo = new FileInfo(srcMenuDarkCSS);
                        if (destMenuDarkCSSFileInfo.LastWriteTime.CompareTo(srcMenuDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuDarkCSS, destMenuDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuDarkCSS, destMenuDarkCSS, true);
                    }
                }

                var destMenuLightJS = workspace + "menu_light.js";
                var srcMenuLightJS = Globals.DefaultWorkspacePath + "menu_light.js";
                if (File.Exists(srcMenuLightJS))
                {
                    if (File.Exists(destMenuLightJS))
                    {
                        FileInfo destMenuLightJSFileInfo = new FileInfo(destMenuLightJS);
                        FileInfo srcMenuLightJSFileInfo = new FileInfo(srcMenuLightJS);
                        if (destMenuLightJSFileInfo.LastWriteTime.CompareTo(srcMenuLightJSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuLightJS, destMenuLightJS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuLightJS, destMenuLightJS, true);
                    }
                }

                var destMenuDarkJS = workspace + "menu_dark.js";
                var srcMenuDarkJS = Globals.DefaultWorkspacePath + "menu_dark.js";
                if (File.Exists(srcMenuDarkJS))
                {
                    if (File.Exists(destMenuDarkJS))
                    {
                        FileInfo destMenuDarkJSFileInfo = new FileInfo(destMenuDarkJS);
                        FileInfo srcMenuDarkJSFileInfo = new FileInfo(srcMenuDarkJS);
                        if (destMenuDarkJSFileInfo.LastWriteTime.CompareTo(srcMenuDarkJSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcMenuDarkJS, destMenuDarkJS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcMenuDarkJS, destMenuDarkJS, true);
                    }
                }

                var destPresentationDarkCSS = workspace + "presentation_dark.css";
                var srcPresentationDarkCSS = Globals.DefaultWorkspacePath + "presentation_dark.css";
                if (File.Exists(srcPresentationDarkCSS))
                {
                    if (File.Exists(destPresentationDarkCSS))
                    {
                        FileInfo srcPresentationDarkCSSFileInfo = new FileInfo(srcPresentationDarkCSS);
                        FileInfo destPresentationDarkCSSFileInfo = new FileInfo(destPresentationDarkCSS);
                        if (destPresentationDarkCSSFileInfo.LastWriteTime.CompareTo(srcPresentationDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcPresentationDarkCSS, destPresentationDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcPresentationDarkCSS, destPresentationDarkCSS, true);
                    }
                }

                var destPresentationLightCSS = workspace + "presentation_light.css";
                var srcPresentationLightCSS = Globals.DefaultWorkspacePath + "presentation_light.css";
                if (File.Exists(srcPresentationLightCSS))
                {
                    if (File.Exists(destPresentationLightCSS))
                    {
                        FileInfo destPresentationLightCSSFileInfo = new FileInfo(destPresentationLightCSS);
                        FileInfo srcPresentationLightCSSFileInfo = new FileInfo(srcPresentationLightCSS);
                        if (destPresentationLightCSSFileInfo.LastWriteTime.CompareTo(srcPresentationLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcPresentationLightCSS, destPresentationLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcPresentationLightCSS, destPresentationLightCSS, true);
                    }
                }

                var destHelpHtml = workspace + "Help~.html";
                var srcHelpHtml = Globals.DefaultWorkspacePath + "Help~.html";
                if (File.Exists(srcHelpHtml))
                {
                    if (File.Exists(destHelpHtml))
                    {
                        FileInfo srcHelpHtmlFileInfo = new FileInfo(srcHelpHtml);
                        FileInfo destHelpHtmlFileInfo = new FileInfo(destHelpHtml);
                        if (destHelpHtmlFileInfo.LastWriteTime.CompareTo(srcHelpHtmlFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcHelpHtml, destHelpHtml, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcHelpHtml, destHelpHtml, true);
                    }
                }

                CopyHighLightResourceFiles(new DirectoryInfo(Globals.DefaultWorkspacePath + "Highlight~\\"), new DirectoryInfo(workspace + "Highlight~\\"));
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        private static string CopyHighLightResourceFiles(DirectoryInfo srcDi, DirectoryInfo destDi)
        {
            if (srcDi == null || srcDi.Exists == false) return "";

            try
            {
                if (srcDi == null || srcDi.Exists == false || destDi == null) return "";
                if (destDi.Exists == false)
                {
                    Directory.CreateDirectory(destDi.FullName);
                }

                var diTexts = Directory.GetDirectories(srcDi.FullName);
                foreach (var diText in diTexts)
                {
                    var subSrcDi = new DirectoryInfo(diText);
                    var newDestDi = new DirectoryInfo(destDi.FullName + "\\" + subSrcDi.Name);
                    if (newDestDi.Exists == false)
                    {
                        Directory.CreateDirectory(newDestDi.FullName);
                    }
                    CopyHighLightResourceFiles(subSrcDi, newDestDi);
                }

                var fiTexts = Directory.GetFiles(srcDi.FullName);
                foreach (var fiText in fiTexts)
                {
                    var subSrcFi = new FileInfo(fiText);
                    if (subSrcFi.Exists == false) continue;

                    var newDestFi = new FileInfo(destDi.FullName + "\\" + subSrcFi.Name);
                    if (newDestFi.Exists == false)
                    {
                        File.Copy(subSrcFi.FullName, newDestFi.FullName, true);
                    }
                    else
                    {
                        if (newDestFi.LastWriteTime.CompareTo(subSrcFi.LastWriteTime) < 0)
                        {
                            File.Copy(subSrcFi.FullName, newDestFi.FullName, true);
                        }
                    }
                }

                return "";
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        public void LoadWorkspace(string path = null)
        {
            App.WorkspaceConfigManager = null;//刷新配置管理器。

            //载入工作区
            var destWorkspaceFolder = LoadWorkspaceDirectory(path);

            //载入工作区 Chm 编译选项。
            LoadWorkspaceChmCompileOptions();

            //刷新工作区管理器
            if (workspaceManager.LoadWorkspaceFromXml(null) == false)
            {
                WorkspaceManager.RefreshWorkspaceTreeView(null);         //已限制
            }

            //载入工作区 Html 编译选项。
            LoadWorkspaceHtmlCompileOptions();

            // [废弃]刷新工作区管理器中各项目的折叠状态。
            // LoadWorkspaceCollapseStatus();

            LoadRememberOpenedFiles();//会自动判断要不要载入。

            WriteHistoryWorkspacesFile(destWorkspaceFolder);

            RefreshWorkspaceHistoryList();

            //tvWorkDirectory.IsEnabled = true;

            if (this.PerspectiveMode == Perspective.CompareMode)
            {
                var activeEditor = ActivedEditor;
                if (activeEditor != null)
                {
                    activeEditor.SendToRightCompareArea();
                }
            }

            TryCreatoDefaultMetaFileForNewWorkspaceDirectory();

            this.Title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace) + " - " + Globals.AppName;
        }

        /// <summary>
        /// 打开指定的工作区目录。
        /// </summary>
        /// <param name="path">如指定此参数，优先打开此参数指定的目录。</param>
        /// <returns></returns>
        private string LoadWorkspaceDirectory(string path = null)
        {
            var eventArgs = new WorkspaceChangedEventArgs()
            {
                OldWorkspaceDirectoryFullPath = Globals.PathOfWorkspace,
                NewWorkspaceDirectoryFullPath = Globals.PathOfWorkspace,
            };
            //OnWorkspaceChanging(this, eventArgs);  // 仅只在 LoadDefaultWorkspace() 方法调用一次，似乎没必要激发此事件。

            //载入指定的工作区目录
            string workspace;
            if (Directory.Exists(path))
            {
                workspace = path;
            }
            else workspace = App.ConfigManager.Get("Workspace");

            if (Directory.Exists(workspace))
            {
                if (workspace.EndsWith("\\") == false) workspace += "\\";
                if (Directory.Exists(workspace))
                {
                    Globals.PathOfWorkspace = workspace;
                }

                //用于判断当前工作区目录是否已被打开，以便提示用户不要重复打开工作区
                File.WriteAllText(Globals.PathOfWorkspace + "~.editing", "This workspace is in editing.");
            }

            //检查工作区目录是否存在。
            if (Directory.Exists(Globals.PathOfWorkspace) == false)
            {
                try
                {
                    Directory.CreateDirectory(Globals.PathOfWorkspace);

                    //检查必须的文件是否存在。
                    CopyCssAndResourceFiles(Globals.PathOfWorkspace);
                }
                catch (System.Exception ex)
                {
                    LMessageBox.Show("未能在创建工作区目录。这可能是因为Windows系统权限造成的。一般来说，不应将工作区指定在系统目录下。异常消息如下：\r\n" +
                        ex.Message + "\r\n" + ex.StackTrace,
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
            else
            {
                if (Globals.PathOfWorkspace.ToLower() != Globals.DefaultWorkspacePath.ToLower())
                {
                    //检查必须的文件是否存在。
                    CopyCssAndResourceFiles(Globals.PathOfWorkspace);
                }
            }

            tbxPathOfWorkspace.Text = Globals.PathOfWorkspace;
            AddJumpTask(Globals.PathOfWorkspace);

            OnWorkspaceChanged(this, eventArgs);

            return Globals.PathOfWorkspace;
        }

        private void AddJumpTask(string workspacePath)
        {
            if (Directory.Exists(workspacePath) == false) return;
            if (workspacePath[workspacePath.Length - 1] != Path.DirectorySeparatorChar)
                workspacePath += Path.DirectorySeparatorChar;

            DirectoryInfo diWorkspace = new DirectoryInfo(workspacePath);

            #region 在任务栏JumpList中添加对应项。
            JumpTask jumpTask1 = new JumpTask();
            // Get the path to Calculator and set the JumpTask properties.
            jumpTask1.ApplicationPath = Globals.AppFullPath;
            jumpTask1.WorkingDirectory = Globals.InstalledPath;
            jumpTask1.IconResourcePath = Globals.AppFullPath;
            jumpTask1.Title = diWorkspace.Name;
            jumpTask1.Description = diWorkspace.FullName;
            jumpTask1.Arguments = diWorkspace.FullName.Contains(" ") ? ("\"" + diWorkspace.FullName + "\"") : diWorkspace.FullName;
            var jumpList = JumpList.GetJumpList(App.Current);

            var b = workspacePath.ToLower();

            if (b.StartsWith("\"")) b = b.Substring(1);
            if (b.EndsWith("\"")) b = b.Substring(0, b.Length - 1);

            if (b.EndsWith("\\") == false) b += "\\";

            for (int i = jumpList.JumpItems.Count - 1; i >= 0; i--)
            {
                if (!(jumpList.JumpItems[i] is JumpTask item)) continue;

                var a = item.Description.ToLower();        //用 Arguments 老是出错。

                if (a.StartsWith("\"")) a = a.Substring(1);
                if (a.EndsWith("\"")) a = a.Substring(0, a.Length - 1);

                if (a.EndsWith("\\") == false) a += "\\";
                if (a == b)
                {
                    jumpList.JumpItems.RemoveAt(i);
                }
            }

            JumpList.AddToRecentCategory(jumpTask1);
            jumpList.Apply();

            #endregion
        }

        private StartWorkspaceSelector startWorkspaceSelector = null;

        public StartWorkspaceSelector StartWorkspaceSelector
        {
            get { return this.startWorkspaceSelector; }
            set { this.startWorkspaceSelector = value; }
        }

        internal void LoadPythonScriptFiles()
        {
            lbxPythonScripts.Items.Clear();
            pyScriptsToolBar.Items.Clear();

            cmGlobalsPythonScripts.Items.Clear();

            var pythonScriptsDirectory = new DirectoryInfo(Globals.PathOfPythonScripts);
            if (pythonScriptsDirectory.Exists)
            {
                var pythonFiles = pythonScriptsDirectory.GetFiles();
                var pythonFilesList = new List<FileInfo>();
                foreach (var fi in pythonFiles)
                {
                    pythonFilesList.Add(fi);
                }

                //pythonFilesList.Sort(new CmparsionLastWriteTime());
                //排序时改以组名优先，再结合时间。

                var pyItemsList = new List<PythonScriptListItem>();

                foreach (var fi in pythonFilesList)
                {
                    if (fi.Extension.ToLower() != ".py") continue;
                    if (showLmePyScripts == false && fi.Name.ToLower().StartsWith("lme_")) continue;

                    string title = "Python 脚本";
                    string description = "<无描述文本>";
                    string callMode = "0";
                    string foregroundText = "Black";
                    string backgroundText = "Transparent";
                    string groupName = "";
                    string shortCutText = "";
                    string toolBarButtonText = "";
                    Brush foreground, background;
                    LoadPythonScriptProperties(fi, ref title, ref description, ref callMode,
                        ref foregroundText, ref backgroundText, ref shortCutText, ref toolBarButtonText, out foreground, out background, ref groupName);

                    var psli = new PythonScriptListItem(title, description, callMode, fi.FullName, shortCutText, toolBarButtonText, foreground, background, groupName)
                    {
                        LastWriteTime = fi.LastWriteTime,
                        Location = PythonScriptLocation.Global,
                    };

                    //lbxPythonScripts.Items.Add(psli);
                    pyItemsList.Add(psli);

                    if (string.IsNullOrWhiteSpace(toolBarButtonText) == false)
                    {
                        var btn = new Button()
                        {
                            FontSize = 14,
                            Content = toolBarButtonText,
                            Height = 20,
                            Padding = new Thickness(4, 0, 4, 0),
                            Style = TryFindResource("LSquareButtonStyle") as Style,
                            BorderThickness = new Thickness(1, 1, 1, 1),
                            Margin = new Thickness(2, 2, 2, 0),
                            FontWeight = FontWeights.Normal,
                            Tag = psli,
                        };

                        if (string.IsNullOrWhiteSpace(psli.ShortCutText))
                        {
                            btn.ToolTip = psli.Title;
                        }
                        else
                        {
                            btn.ToolTip = "【" + psli.Title + "】";
                        }

                        var btnContextMenu = new ContextMenu();
                        var miOpenScript = new MenuItem()
                        {
                            Header = $"编辑 {psli.Title}(_E)",
                            Style = TryFindResource("MetroMenuItem") as Style,
                            Tag = psli,
                        };
                        miOpenScript.Click += MiOpenScript_Click; ;
                        btnContextMenu.Items.Add(miOpenScript);
                        btn.ContextMenu = btnContextMenu;
                        btn.MouseRightButtonDown += Btn_MouseRightButtonDown;

                        if (string.IsNullOrWhiteSpace(shortCutText) == false && shortCutText != "无")
                        {
                            btn.ToolTip = $"【{shortCutText.ToUpper()}】{title}";
                        }
                        else
                        {
                            btn.ToolTip = title;
                        }

                        btn.Click += Btn_Click;
                        pyScriptsToolBar.Items.Add(btn);
                    }
                }

                pyItemsList.Sort(new ComparsionPythonScriptItem());

                var preGroupName = "";
                var groupIndex = 0;
                foreach (var pi in pyItemsList)
                {
                    if (pi.GroupName != preGroupName)
                    {
                        groupIndex++;
                        var newListBoxItem = new ListBoxItem()
                        {
                            Content = $"分组{groupIndex}：{pi.GroupName}",
                            Background = L.ThemeBrown,
                            Foreground = Brushes.White,
                            IsEnabled = false,
                            FontSize = 12,
                            Height = 16,
                            MaxWidth = 396,
                        };
                        if (string.IsNullOrWhiteSpace(pi.GroupName))
                        {
                            newListBoxItem.Content = "[未分组]";
                        }
                        lbxPythonScripts.Items.Add(newListBoxItem);
                        preGroupName = pi.GroupName;
                    }
                    lbxPythonScripts.Items.Add(pi);
                }
            }

            if (pyScriptsToolBar.Items.Count <= 0)
            {
                pyScriptsToolBar.Visibility = Visibility.Collapsed;
                mGlobalPythonScripts.Visibility =
                    cmGlobalsPythonScripts.Visibility = Visibility.Collapsed;
            }
            else
            {
                pyScriptsToolBar.Visibility = Visibility.Visible;
                foreach (var ue in pyScriptsToolBar.Items)
                {
                    var btn = ue as Button;
                    if (btn == null) continue;

                    var psmi = new PythonScriptMenuItem(btn);
                    cmGlobalsPythonScripts.Items.Add(psmi);
                }
                mGlobalPythonScripts.Visibility =
                    cmGlobalsPythonScripts.Visibility = Visibility.Visible;
            }
        }

        internal void LoadWorkspacePythonScriptFiles()
        {
            lbxWorkspacePythonScripts.Items.Clear();
            workspacePyScriptsToolBar.Items.Clear();

            cmWorkspacePythonScripts.Items.Clear();

            var workspacePythonScriptsDirectory = new DirectoryInfo(Globals.PathOfWorkspacePythonScripts);
            if (workspacePythonScriptsDirectory.Exists)
            {
                var workspacePythonFiles = workspacePythonScriptsDirectory.GetFiles();
                var workspacePythonFilesList = new List<FileInfo>();
                foreach (var fi in workspacePythonFiles)
                {
                    workspacePythonFilesList.Add(fi);
                }

                //workspacePythonFilesList.Sort(new CmparsionLastWriteTime());
                //排序时改以组名优先，再结合时间。

                var workspacePyItemsList = new List<PythonScriptListItem>();

                foreach (var fi in workspacePythonFilesList)
                {
                    if (fi.Extension.ToLower() != ".py") continue;
                    if (showLmePyScripts == false && fi.Name.ToLower().StartsWith("lme_")) continue;

                    string title = "Python 脚本";
                    string description = "<无描述文本>";
                    string callMode = "0";
                    string foregroundText = "Black";
                    string backgroundText = "Transparent";
                    string groupName = "";
                    string shortCutText = "";
                    string toolBarButtonText = "";
                    Brush foreground, background;
                    LoadPythonScriptProperties(fi, ref title, ref description, ref callMode,
                        ref foregroundText, ref backgroundText, ref shortCutText, ref toolBarButtonText, out foreground, out background, ref groupName);

                    var psli = new PythonScriptListItem(title, description, callMode, fi.FullName, shortCutText, toolBarButtonText, foreground, background, groupName)
                    {
                        LastWriteTime = fi.LastWriteTime,
                        Location = PythonScriptLocation.Workspace,
                    };

                    //lbxWorkspacePythonScripts.Items.Add(psli);
                    workspacePyItemsList.Add(psli);

                    if (string.IsNullOrWhiteSpace(toolBarButtonText) == false)
                    {
                        var btn = new Button()
                        {
                            FontSize = 14,
                            Content = toolBarButtonText,
                            Height = 20,
                            Padding = new Thickness(4, 0, 4, 0),
                            Style = TryFindResource("LSquareButtonStyle") as Style,
                            BorderThickness = new Thickness(1, 1, 1, 1),
                            Margin = new Thickness(2, 2, 2, 0),
                            FontWeight = FontWeights.Normal,
                            Tag = psli,
                        };

                        if (string.IsNullOrWhiteSpace(psli.ShortCutText))
                        {
                            btn.ToolTip = psli.Title;
                        }
                        else
                        {
                            btn.ToolTip = "【" + psli.Title + "】";
                        }

                        var btnContextMenu = new ContextMenu();
                        var miOpenScript = new MenuItem()
                        {
                            Header = $"编辑 {psli.Title}(_E)",
                            Style = TryFindResource("MetroMenuItem") as Style,
                            Tag = psli,
                        };
                        miOpenScript.Click += MiOpenScript_Click; ;
                        btnContextMenu.Items.Add(miOpenScript);
                        btn.ContextMenu = btnContextMenu;
                        btn.MouseRightButtonDown += Btn_MouseRightButtonDown;

                        if (string.IsNullOrWhiteSpace(shortCutText) == false && shortCutText != "无")
                        {
                            btn.ToolTip = $"【{shortCutText.ToUpper()}】{title}";
                        }
                        else
                        {
                            btn.ToolTip = title;
                        }

                        btn.Click += Btn_Click;
                        workspacePyScriptsToolBar.Items.Add(btn);
                    }
                }

                workspacePyItemsList.Sort(new ComparsionPythonScriptItem());

                var preGroupName = "";
                var groupIndex = 0;
                foreach (var pi in workspacePyItemsList)
                {
                    if (pi.GroupName != preGroupName)
                    {
                        groupIndex++;
                        var newListBoxItem = new ListBoxItem()
                        {
                            Content = $"分组{groupIndex}：{pi.GroupName}",
                            Background = L.ThemeBrown,
                            Foreground = Brushes.White,
                            IsEnabled = false,
                            FontSize = 12,
                            Height = 16,
                            MaxWidth = 396,
                        };
                        if (string.IsNullOrWhiteSpace(pi.GroupName))
                        {
                            newListBoxItem.Content = "[未分组]";
                        }
                        lbxWorkspacePythonScripts.Items.Add(newListBoxItem);
                        preGroupName = pi.GroupName;
                    }
                    lbxWorkspacePythonScripts.Items.Add(pi);
                }
            }

            if (workspacePyScriptsToolBar.Items.Count <= 0)
            {
                workspacePyScriptsToolBar.Visibility = Visibility.Collapsed;
                mWorkspacePythonScripts.Visibility =
                    cmWorkspacePythonScripts.Visibility = Visibility.Collapsed;
            }
            else
            {
                workspacePyScriptsToolBar.Visibility = Visibility.Visible;
                foreach (var ue in workspacePyScriptsToolBar.Items)
                {
                    var btn = ue as Button;
                    if (btn == null) continue;

                    var psmi = new PythonScriptMenuItem(btn);
                    cmWorkspacePythonScripts.Items.Add(psmi);
                }
                mWorkspacePythonScripts.Visibility =
                    cmWorkspacePythonScripts.Visibility = Visibility.Visible;
            }
        }

        private void Btn_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            var btn = sender as Button;
            if (btn == null || btn.ContextMenu == null) return;

            btn.ContextMenu.IsOpen = true;
        }

        private void MiOpenScript_Click(object sender, RoutedEventArgs e)
        {
            var mi = sender as MenuItem;
            if (mi == null) return;

            var ownerItem = mi.Tag as PythonScriptListItem;
            if (ownerItem == null) return;

            ownerItem.EditScriptFile();
        }

        private void Btn_Click(object sender, RoutedEventArgs e)
        {
            var btn = sender as Button;
            if (btn == null) return;

            var psli = btn.Tag as PythonScriptListItem;
            if (psli == null) return;

            psli.RunScript();
        }

        private void LoadPythonScriptProperties(FileInfo fi, ref string title, ref string description,
            ref string callMode, ref string foregroundText, ref string backgroundText, ref string shortCutText, ref string toolBarButtonText,
            out Brush foreground, out Brush background, ref string groupName)
        {
            using (StreamReader sr = new StreamReader(fi.FullName))
            {
                var line = sr.ReadLine();
                int count = 1;
                while (line != null && count <= 50)
                {
                    TryReadPythonScriptTitle(line, ref title);
                    TryReadPythonScriptDescription(line, ref description);
                    TryReadPythonScriptCallMode(line, ref callMode);
                    TryReadPythonScriptShortCut(line, ref shortCutText);
                    TryReadPythonScriptItemForeground(line, ref foregroundText);
                    TryReadPythonScriptItemBackground(line, ref backgroundText);
                    TryReadPythonScriptItemGroupName(line, ref groupName);
                    if (string.IsNullOrEmpty(groupName))
                    {
                        if (fi.Name.ToLower().StartsWith("lme_"))
                        {
                            groupName = "~LME_内置示例~";
                        }
                    }
                    TryReadPythonScriptToolBarButtonText(line, ref toolBarButtonText);

                    if (string.IsNullOrWhiteSpace(line) == false) count++;

                    line = sr.ReadLine();
                }
            }

            if (string.IsNullOrWhiteSpace(title)) title = "未设置标题";
            if (string.IsNullOrWhiteSpace(description)) description = "<无描述文本>";
            if (string.IsNullOrWhiteSpace(callMode)) callMode = "0";
            if (string.IsNullOrWhiteSpace(shortCutText)) shortCutText = "";

            foreground = BrushManager.GetBrush(foregroundText);
            if (foreground == null) foreground = BrushManager.GetBrushByChineseName(foregroundText);

            background = BrushManager.GetBrush(backgroundText);
            if (background == null) background = BrushManager.GetBrushByChineseName(backgroundText);
        }

        public bool TryReadPythonScriptTitle(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}脚本标题[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptDescription(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}脚本描述[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptCallMode(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}调用方式[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptShortCut(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}快捷键[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptItemForeground(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}前景色[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptItemBackground(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}背景色[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptItemGroupName(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}分{0,1}组名称{0,1}[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        public bool TryReadPythonScriptToolBarButtonText(string line, ref string settingValue)
        {
            var reg = new Regex(@"^#[ 　\t]{0,}(工具[栏|条]{0,1}){0,1}按钮文本[：:][ 　\t]{0,}");
            var match = reg.Match(line);
            if (match.Success)
            {
                settingValue = line.Substring(match.Length).Trim(new char[] { ' ', '　', '\t', });
                return true;
            }
            return false;
        }

        /// <summary>
        /// 在主窗口载入后进行一些初始化操作。
        /// </summary>
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 解决向下拖动分隔条越界问题
            var rdLeftToolsMaxHeightBinding = new Binding();
            rdLeftToolsMaxHeightBinding.Source = tcRightToolBar;
            rdLeftToolsMaxHeightBinding.Path = new PropertyPath("ActualHeight");
            rdLeftToolsMaxHeightBinding.Converter = new Converter.MaxHeightOfLeftToolsBarConverter();
            BindingOperations.SetBinding(rdLeftToolsTop, RowDefinition.MaxHeightProperty, rdLeftToolsMaxHeightBinding);
            BindingOperations.SetBinding(rdResourcePreviewArea, RowDefinition.MaxHeightProperty, rdLeftToolsMaxHeightBinding);

            TryToGetJsonOfLmeProject();

            #region 更改几个组合框下拉箭头的配色
            var tbtnPath1 = FindVisualChildItem<System.Windows.Shapes.Path>(fontFamilyList, "BtnArrow");
            if (tbtnPath1 != null) tbtnPath1.Fill = Brushes.White;

            var tbtnPath2 = FindVisualChildItem<System.Windows.Shapes.Path>(cmbSearchArea2, "BtnArrow");
            if (tbtnPath2 != null) tbtnPath2.Fill = Brushes.White;

            var tbtnPath3 = FindVisualChildItem<System.Windows.Shapes.Path>(cmbPerspective, "BtnArrow");
            if (tbtnPath3 != null) tbtnPath3.Fill = Brushes.White;
            #endregion

            #region 更改 Metro 菜单 PopUp 的显示位置，以保持风格一致
            foreach (var ue in mainMenu.Items)
            {
                if (!(ue is MenuItem mi)) continue;

                var partPopup = FindVisualChildItem<Popup>(mi, "PART_Popup");
                if (partPopup != null)
                {
                    // 这里如果去除，第一次弹出的位置就不准确
                    partPopup.Placement = PlacementMode.Absolute;
                    var point = mi.PointToScreen(new Point(0, 0));
                    partPopup.HorizontalOffset = point.X + 1;
                    partPopup.VerticalOffset = point.Y + mi.ActualHeight - 2;
                }

                mi.MouseEnter += Mi_MouseEnter;
                mi.MouseLeave += Mi_MouseLeave;
                mi.SubmenuOpened += Mi_SubmenuOpened;  // 少了这行，之后弹出的位置可能不准确
                                                       //此方案貌似仍然有些潜在的问题，可惜重定义 MetroContextStyle 模板代价太高。
            }
            #endregion

            #region 复制 LME 内置的 Python 脚本到用户脚本目录（按需要）

            try
            {
                var srcDirectory = new DirectoryInfo(Globals.PathOfLmedPyScripts);
                if (srcDirectory.Exists)
                {
                    var lmePyScriptsPathList = new List<string>();

                    var lmeScriptsInfos = srcDirectory.GetFiles("Lme_*.py", SearchOption.TopDirectoryOnly);

                    if (lmeScriptsInfos.Length > 0)
                    {
                        var destDirectory = new DirectoryInfo(Globals.PathOfPythonScripts);

                        foreach (var srcFileInfo in lmeScriptsInfos)
                        {
                            var destFileInfo = new FileInfo(Globals.PathOfPythonScripts + srcFileInfo.Name);
                            if (destFileInfo.Exists == false)
                            {
                                File.Copy(srcFileInfo.FullName, destFileInfo.FullName);
                                continue;
                            }

                            if (destFileInfo.LastWriteTime.CompareTo(srcFileInfo.LastWriteTime) < 0)  // 目标文件最后写入时间早于安装目录下同名文件的时间
                            {
                                File.Copy(srcFileInfo.FullName, destFileInfo.FullName);
                                continue;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            #endregion 复制 LME 内置的 Python 脚本到用户脚本目录

            var showLmePyScriptsText = App.ConfigManager.Get("ShowLmePyScripts");
            bool slps;
            if (bool.TryParse(showLmePyScriptsText, out slps))
            {
                this.showLmePyScripts = slps;
            }
            ckxShowLmePyScripts.IsChecked = this.showLmePyScripts;

            LoadPythonScriptFiles();

            var askEnglishFileNameText = App.ConfigManager.Get("AskEnglishFileName");
            bool aefnt;
            if (bool.TryParse(askEnglishFileNameText, out aefnt))
            {
                this.askEnglishFileName = aefnt;
            }
            miAskEnglishFileName.IsChecked = askEnglishFileName;

            try
            {
                //载入前导字符串的正则表达式。
                var manager = App.ConfigManager;
                if (manager != null)
                {
                    leaderTextRegsA = manager.Get("LeaderTextRegsA");
                    leaderTextRegsB = manager.Get("LeaderTextRegsB");
                    ReplaceLeaderChar = manager.Get("ReplaceLeaderChar");  //这里用属性，不用字段
                }


                //载入程序上次关闭前使用的透视图模式
                LoadRememberedPerspectiveMode();
                cmbPerspective.SelectionChanged += cmbPerspective_SelectionChanged;

                StringBuilder sb = new StringBuilder();
                if (Globals.args != null && Globals.args.Length > 0)
                {
                    for (int i = 0; i < Globals.args.Length; i++)
                    {
                        var s = Globals.args[i];
                        sb.Append($"args[{i}]" + s + "\r\n");
                    }
                }

                var cmdParameterPath = Globals.CmdParameterString;

                //MessageBox.Show("1 " + cmdParameterPath);
                //测试用代码。使用项目属性→调试→命令行参数并不能解决全部问题。

                cmdParameterPath = cmdParameterPath.Trim(new char[] { '\"' });

                if (string.IsNullOrWhiteSpace(cmdParameterPath) == false)
                {
                    if (Directory.Exists(cmdParameterPath) == false)
                    {
                        try
                        {
                            LMessageBox.Show("磁盘上不存在指定路径的目录，将自动尝试创建相应目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);

                            Directory.CreateDirectory(cmdParameterPath);
                        }
                        catch
                        {
                            cmdParameterPath = Globals.DefaultWorkspacePath;
                            LMessageBox.Show("磁盘上不存在指定路径的目录且无法创建。将打开默认的初始工作区目录。\r\n\r\n★　建议尽快更改工作区目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }

                    }
                }

                //目录带空格易出错，警告用户
                if (cmdParameterPath.Contains(" "))
                {
                    LMessageBox.Show("指定工作区目录短名中带空格，这容易导致在将工作区编译为 CHM 文件时出错。\r\n" +
                        "　　您可以考虑在创建 CHM 工程文件后，使用 Html Help Workshop 手动打开工程文件并在路径两侧加上半角双引号来规避此错误。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning, "PathWithBlanks");
                }

                //MessageBox.Show("2 " + cmdParameterPath);
                //测试用代码。使用项目属性→调试→命令行参数并不能解决全部问题。

                if (cmdParameterPath.StartsWith("\"") && cmdParameterPath.EndsWith("\""))
                {
                    cmdParameterPath = cmdParameterPath.Substring(1, cmdParameterPath.Length - 2);
                }

                if (Directory.Exists(cmdParameterPath))
                {
                    LoadWorkspace(cmdParameterPath);
                    return;
                }

                if (this.SelectWorkspaceAtStart &&
                    File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    string[] workspaces = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                    if (workspaces.Length > 0)
                    {
                        startWorkspaceSelector = new LunarMarkdownEditor.StartWorkspaceSelector()
                        {
                            Owner = this,
                            WindowStartupLocation = WindowStartupLocation.CenterScreen,
                        };

                        startWorkspaceSelector.Show();
                    }
                    else
                    {
                        LoadWorkspace();
                    }
                }
                else
                {
                    LoadWorkspace();
                }

                this.mainTabControl.SelectionChanged += mainTabControl_SelectionChanged;

                LoadUserEnvironmentValues();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }

            #region 载入自启动脚本，以便添加菜单、按钮啥的。
            // 为什么放弃此一目标？
            // 1. 没有绝对必要——完全可以让 C# 根据脚本中的注释来自动添加对应的工具条按钮、菜单项。
            // 2. 无法解决“从 IronPython 调用 C# 方法出现的异常无法截获”的问题。
            // 3. 添加自动脚本时，会出现程序启动速度明显变慢的问题。
            //    这是因为要载入相关的库——程序启动后，第一次执行脚本就是会慢的。
            //List<PythonScriptListItem> autoLoadScriptItems = new List<PythonScriptListItem>();
            //foreach (var i in lbxPythonScripts.Items)
            //{
            //    var psi = i as PythonScriptListItem;
            //    if (psi == null) continue;
            //    if (psi.CallMode == PythonScriptListItem.PythonScriptCallMode.AutoLoadWhenStart)
            //    {
            //        autoLoadScriptItems.Add(psi);
            //    }
            //}

            //if (autoLoadScriptItems.Count > 0)
            //{
            //    ScriptEngine engine = Python.CreateEngine();
            //    ScriptScope scope = engine.CreateScope();

            //    foreach (var autoLoadItem in autoLoadScriptItems)
            //    {
            //        var sourceCode = engine.CreateScriptSourceFromFile(autoLoadItem.FullPath);
            //        // 运行脚本
            //        scope.SetVariable("mainWindow", this);

            //        // 重设搜索路径，默认的不管用
            //        var pathList = engine.GetSearchPaths();
            //        pathList.Add(Globals.PathOfPythonScripts);
            //        engine.SetSearchPaths(pathList);

            //        var streamOut = new MemoryStream();
            //        var streamErr = new MemoryStream();
            //        engine.Runtime.IO.SetOutput(streamOut, Encoding.Default);
            //        engine.Runtime.IO.SetErrorOutput(streamErr, Encoding.Default);

            //        sourceCode.Execute<string>(scope);
            //    }
            //}
            #endregion

            this.Activated += MainWindow_Activated;
        }

        /// <summary>
        /// 给 Python 脚本调用的。
        /// </summary>
        public MarkdownEditorBase ActiveTextEditor
        {
            get
            {
                var ae = ActivedEditor;
                if (ae != null) return ae.EditorBase;

                return null;
            }
        }

        #region 方便 Python 脚本添加自定义按钮
        // 允许用户添加工具条按钮、自定义菜单项在执行按钮（菜单项）事件时一旦源码在调用 C# 方法时写错，
        // 程序会直接崩溃——无法截获异常。
        //public DockPanel MainToolBarPanel { get { return mainToolBarPanel; } }
        //public ToolBarTray MainToolBarTray { get { return mainToolBarTray; } }
        //public ToolBar MainToolBar { get { return mainToolBar; } }
        #endregion

        #region 这是为了方便 Python 脚本添加自定义菜单项
        // 允许用户添加工具条按钮、自定义菜单项在执行按钮（菜单项）事件时一旦源码在调用 C# 方法时写错，
        // 程序会直接崩溃——无法截获异常。
        //public Menu MainMenu { get { return mainMenu; } }
        //public MenuItem MFile { get { return mFile; } }
        //public MenuItem MEdit { get { return mEdit; } }
        //public MenuItem MInsert { get { return mInsert; } }
        //public MenuItem MView { get { return mView; } }
        //public MenuItem MFormat { get { return mFormat; } }
        //public MenuItem MExams { get { return mExams; } }
        //public MenuItem MCompile { get { return mCompile; } }
        //public MenuItem MPresentation { get { return mPresentation; } }
        //public MenuItem MPreference { get { return mPreference; } }
        //public MenuItem MTools { get { return mTools; } }
        //public MenuItem MHelp { get { return mHelp; } }
        #endregion

        private void BtnAddNewPythonScript_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                FileInfo[] files;
                if (tcPythonScripts.SelectedItem == tiWorkspacePythonScripts)
                {
                    files = new DirectoryInfo(Globals.PathOfWorkspacePythonScripts).GetFiles();
                }
                else  // tiGlobalPythonScripts
                {
                    files = new DirectoryInfo(Globals.PathOfPythonScripts).GetFiles();
                }

                int scriptsCount = 0;
                foreach (var fi in files)
                {
                    if (fi.Extension.ToLower() == ".py")
                    {
                        scriptsCount++;
                    }
                }

                //var shortName = InputBox.Show(Globals.AppName, "　　请输入脚本文件名：",
                //    $"新脚本{(scriptsCount > 0 ? (scriptsCount + 1).ToString() : string.Empty)}",
                //    true, "注意：文件名不能以“Lme_”开头！");

                var fInfo = EntryInfoInputBox.Show(Globals.AppName, "请输入脚本标题文本【可以中文】：", "请输入脚本文件名【英文】：", "新脚本",
                    $"new_script{(scriptsCount > 0 ? ("_" + (scriptsCount + 1).ToString()) : string.Empty)}",
                    "★　注：①脚本文件名用英文便于在脚本间引用。\r\n　　　　②文件名不能以“Lme_”开头！", false, this);

                if (fInfo == null) return;

                var shortName = fInfo.EntryName;
                if (string.IsNullOrEmpty(shortName)) return;

                var title = fInfo.Title;

                shortName = shortName.ToLower().Replace(' ', '_').Replace('-', '_');  // 考虑到 Python 模块的命名规范，都用小写，以 _ 分割才对。

                // 注意：python 模块文件的命名规范推荐使用 _ 而不是 -。
                //       但是 Html 文件使用 _ 容易与下划线高亮显示效果相冲突，所以用 - 更好。
                //       所以，在对脚本命名时，应使用 _ ，而对 Markdown 文件命名时则尽可能用 - 。

                if (shortName.ToLower().StartsWith("lme_"))
                {
                    LMessageBox.Show("自定义脚本文件名不能以“lme_”开头！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }


                if (shortName.ToLower().EndsWith(".py") == false) shortName += ".py";
                string fullPath;
                if (tcPythonScripts.SelectedItem == tiWorkspacePythonScripts)
                {
                    fullPath = Globals.PathOfWorkspacePythonScripts + shortName;
                }
                else  // tiGlobalPythonScripts
                {
                    fullPath = Globals.PathOfPythonScripts + shortName;
                }

                if (File.Exists(fullPath))
                {
                    var answer = LMessageBox.Show("已存在同名脚本文件，为避免造成损失，操作不能继续进行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                using (StreamWriter sw = new StreamWriter(fullPath))
                {
                    sw.WriteLine("#-*- coding: UTF-8 -*- 此行指定字符编码，勿删！");
                    sw.WriteLine("");
                    sw.WriteLine("# 设置基本信息==============================");
                    sw.WriteLine("");
                    sw.WriteLine($"# 脚本标题：{title}");
                    sw.WriteLine("# 脚本描述：<此处填写描述文本>");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 前景色：黑色");
                    sw.WriteLine("# 背景色：透明色");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 调用方式：0");
                    sw.WriteLine("#           0 表示手动调用，按下对应项目上的按钮执行脚本；");
                    sw.WriteLine("#           1 表示编译Html前自动调用；");
                    sw.WriteLine("#           2 表示编译Html后自动调用——通常不应该使用；");
                    sw.WriteLine("#           3 使用快捷键调用；");
                    sw.WriteLine("#           4 在使用 Html 工厂加工 Html 文件时自动调用；");
                    sw.WriteLine("#           5 按 Enter 键换行时调用；——通常应在末尾设置：“iv_cancel = True”；");
                    //sw.WriteLine("#           4 LME 主窗口载入完成时自动调用；");
                    sw.WriteLine("#     如果此脚本只准备手动调用，可省略上面【调用方式：x】行；");
                    sw.WriteLine("#     所有被标记为“1”的脚本都会在编译Html之前依次调用——“自动调用”时不受“作用范围”影响；");
                    sw.WriteLine("#     所有被标记为“2”的脚本都会在编译Html之后依次调用——“自动调用”时不受“作用范围”影响；");
                    sw.WriteLine("#     所有被标记为“3”的脚本都会在按下指定快捷键后依次调用——这属于“手工调用”，受“作用范围”影响；");
                    sw.WriteLine("#     所有被标记为“4”的脚本都会 Html 工厂加工 Html 文本之后、保存之前依次调用——“自动调用”时不受“作用范围”影响；");
                    sw.WriteLine("#     所有被标记为“5”的脚本都会在按下 Enter 键准备换行时调用（尽量少用）——这属于“手动调用”，受“作用范围”影响；");
                    //sw.WriteLine("#     所有被标记为“4”的脚本都会在 LME 主窗口载入完成时依次调用；");
                    sw.WriteLine("#     另，所有被标记为非“0”的脚本都同时支持手动调用——但最好不要这么干！");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 快捷键：");
                    sw.WriteLine("#     不常用的快捷键要及时清除，避免误操作。");
                    sw.WriteLine("# 工具条按钮文本：");
                    sw.WriteLine("#     工具条按钮文本不为空时，会显示在主窗口工具条上。");
                    sw.WriteLine("#     可以通过类似“某脚本(_X)”这样的格式添加加速键。");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 组名：");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 脚本的标题、描述、调用方式和脚本在列表中的前景色、背景可以修改，但不能更改冒号之前的部分！----");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 变量说明：");
                    sw.WriteLine("# 变量 documentText 中传入的是当前活动文档的文本，或待处理的 Html 文本。");
                    sw.WriteLine("# 变量 inputText 中传入的是要加工的文本，可直接使用。此变量的值可能与 documentText 的值相同。加工好后的文本应在末尾赋值给 outputText 变量。");
                    sw.WriteLine("# 变量 ignoreTextChanging 默认值是 False。 当此变量的值被设置为 true 时，脚本对文本的更改会被忽略。这适用于不需要更改源文本的情况。——例如仅仅只需要用脚本来提取文本的时候。");
                    sw.WriteLine("# 变量 pathToWorkspace 中传入是当前 Markdown 文档到工作区根目录的相对路径。此变量的值通常类似“../../”，表示当前编辑器中的 Markdown 文件相对于工作区根目录的路径；可以利用此变量的路径来引用工作区根目录下的某些文件（例如图片文件）。");
                    sw.WriteLine("# 变量 pathOfWorkspace 中是当前工作区目录的磁盘路径。");
                    sw.WriteLine("# 变量 psArgs 将来会带些其它信息。可以运行〖for m in clr.GetClrType(psArgs.GetType()).GetMethods(): print m〗这行脚本来获取 psArgs 的方法列表。");
                    sw.WriteLine("");
                    sw.WriteLine("# 引入库======================================");
                    sw.WriteLine("import sys ");
                    sw.WriteLine("import clr");
                    sw.WriteLine("clr.AddReference(\"PresentationFramework\")");
                    sw.WriteLine("clr.AddReference(\"PresentationCore\")");
                    sw.WriteLine("from System.Windows import *");
                    sw.WriteLine("from System.Windows.Controls import *");
                    sw.WriteLine("from System.Windows.Media import *");
                    sw.WriteLine("from System.Text.RegularExpressions import *");
                    sw.WriteLine("");
                    sw.WriteLine("clr.AddReference(\"LunarMarkdownEditor\")");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor import Globals");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor import LButton");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor import LMenuItem");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor import LTopMenuItem");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor import LWindow");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor.Utils import Text");
                    sw.WriteLine("from LunarSF.SHomeWorkshop.LunarMarkdownEditor.Utils import TextPiece");
                    sw.WriteLine("");
                    sw.WriteLine("clr.AddReference(\"MahApps.Metro\")");
                    sw.WriteLine("import MahApps.Metro.Controls");
                    sw.WriteLine("");
                    sw.WriteLine("if not 'inputText' in vars():");  // 为方便使用 Visual Studio Code 进行调试
                    sw.WriteLine("    inputText = 'Sample Text'");  // 在这里先加这两行，防止 inputText 未定义
                    sw.WriteLine("");
                    sw.WriteLine("ate = psArgs.Ate");
                    sw.WriteLine("");
                    if (rbtnDefaultScript.IsChecked == true)
                    {
                        sw.WriteLine("# --------------------------------------------");
                        sw.WriteLine("def FormatText(s):  # 可以修改此方法");
                        sw.WriteLine("    r\"\"\"");
                        sw.WriteLine("    示例方法");
                        sw.WriteLine("    ~~~~~~~~");
                        sw.WriteLine("");
                        sw.WriteLine("        此方法目前的功能仅仅是将传入的文本用一对实心方括号括起来并返回而已。");
                        sw.WriteLine("");
                        sw.WriteLine("    :param s: 传入要格式化的字符串。");
                        sw.WriteLine("    :return: 返回格式化后的字符串。");
                        sw.WriteLine("    \"\"\"");
                        sw.WriteLine("    return \"【\" + s + \"】\"");
                        sw.WriteLine("# --------------------------------------------");
                        sw.WriteLine("");
                        sw.WriteLine("");
                        sw.WriteLine("");
                    }
                    sw.WriteLine("");
                    sw.WriteLine("# --------------------------------------------");
                    if (rbtnDefaultScript.IsChecked == true)
                    {
                        sw.WriteLine("# ignoreTextChanging = True  # 默认脚本通常不需要设置此变量。");
                        sw.WriteLine("# 此变量用于在脚本运行结束时，指示 LME 是否用 outputText 的内容替换“作用范围”内的文本。");
                    }
                    else
                    {
                        sw.WriteLine("ignoreTextChanging = True  # 高级脚本通常应设置此变量为 True。");
                        sw.WriteLine("# 此变量为 True 时，脚本执行结束后，");
                        sw.WriteLine("# LME 不会用 outputText 内容替换指定“作用范围”内的文本，");
                        sw.WriteLine("# 这种情况下，所有替换操作均由用户脚本直接操纵编辑器完成。");
                    }
                    sw.WriteLine("");
                    if (rbtnDefaultScript.IsChecked == true)
                    {
                        sw.WriteLine("# iv_cancel = True  # 此变量目前仅用于阻止在编辑器中按下 Enter 键的后续行为。");
                        sw.WriteLine("# 当此变量值为 True 时，按 Enter 键执行脚本后，不会再触发内置的“换行”操作。");
                        sw.WriteLine("# 此变量值可以在其它位置设置，只要脚本执行完毕为 True 就可以达到目的。");
                    }
                    sw.WriteLine("");
                    if (rbtnDefaultScript.IsChecked == true)
                    {
                        sw.WriteLine("outputText = FormatText(inputText)  # 默认脚本可以通常可根据需要修改此行。");
                        sw.WriteLine("# 此变量是否有意义，取决于“ignoreTextChanging”和“iv_cancel”的值。");
                        sw.WriteLine("# 默认脚本通过变量 outputText 传出处理后的文本，");
                        sw.WriteLine("# LME 在脚本执行结束后会用这些文本来替换掉“作用范围”指定的目标文本。");
                    }
                    else
                    {
                        sw.WriteLine("outputText = '' # 高级脚本通常不需要设置此变量的值。如非必要，请勿修改本行。");
                        sw.WriteLine("# 此变量是否有意义，取决于“ignoreTextChanging”和“iv_cancel”的值。");
                        sw.WriteLine("# 高级脚本通常直接操纵编辑器进行文本替换，而不需要通过此变量传出处理过后的文本。");
                        sw.WriteLine("# 所以高级脚本通常会将“ignoreTextChanging”设置为“True”。");
                    }
                    sw.WriteLine("# 如果不需要输出处理后的文本，可改为：outputText = \"\"");
                    sw.WriteLine("# 但必须保留 outputText 变量，否则会导致异常或崩溃！");
                    sw.WriteLine("# --------------------------------------------");
                    sw.WriteLine("# 注意：如果调用方式为 4 或 5，一般情况下不需要 outputText 变量；");
                    sw.WriteLine("# 　　　但如果此时再手工调用此脚本，仍然需要 outputText 变量。");
                    sw.WriteLine("# ============================================");
                    sw.WriteLine("");
                }

                var newItem = new PythonScriptListItem(title, "", "0", fullPath);
                if (tcPythonScripts.SelectedItem == tiWorkspacePythonScripts)
                {
                    newItem.Location = PythonScriptLocation.Workspace;
                    lbxWorkspacePythonScripts.Items.Insert(0, newItem);
                }
                else  // tiGlobalPythonScripts
                {
                    newItem.Location = PythonScriptLocation.Global;
                    lbxPythonScripts.Items.Insert(0, newItem);
                }
                newItem.EditScriptFile();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void Mi_SubmenuOpened(object sender, RoutedEventArgs e)
        {
            if (!(sender is MenuItem mi)) return;

            var partPopup = FindVisualChildItem<Popup>(mi, "PART_Popup");
            if (partPopup != null)
            {
                partPopup.Placement = PlacementMode.Absolute;
                var point = mi.PointToScreen(new Point(0, 0));
                partPopup.HorizontalOffset = point.X + 1;
                partPopup.VerticalOffset = point.Y + mi.ActualHeight - 2;
            }
        }

        private void Mi_MouseLeave(object sender, MouseEventArgs e)
        {
            var mi = sender as MenuItem;
            mi.Foreground = Brushes.White;
            mi.Background = Brushes.Transparent;
        }

        private void Mi_MouseEnter(object sender, MouseEventArgs e)
        {
            var mi = sender as MenuItem;
            mi.Foreground = Brushes.Black;
            mi.Background = Brushes.LightGray;
        }

        private void MainWindow_Activated(object sender, EventArgs e)
        {
            if (startWorkspaceSelector != null && startWorkspaceSelector.Visibility == Visibility.Visible)
            {
                startWorkspaceSelector.Activate();
            }
        }

        private void LoadUserPreference()
        {
            //载入程序上次关闭前使用的透视图模式
            //LoadRememberedPerspectiveMode();
            //cmbPerspective.SelectionChanged += cmbPerspective_SelectionChanged;
            //窗口尚未载入，此时无法获取准确的窗口宽度

            //载入字体列表
            InitializeFontFamilyList();

            //载入默认字体
            this.defaultFontFamily = mainTabControl.FontFamily;
            var defFontFamilyEnName = App.ConfigManager.Get("FontFamily");
            foreach (var item in fontFamilyList.Items)
            {
                if (!(item is ComboBoxItem cbi)) continue;

                if (!(cbi.Content is FontFamilyListItem fli)) continue;

                string enName;
                if (fli.FontFamily.FamilyNames.TryGetValue(System.Windows.Markup.XmlLanguage.GetLanguage("en-us"), out enName))
                {
                    if (defFontFamilyEnName == enName)
                    {
                        this.mainTabControl.FontFamily = fli.FontFamily;
                        this.fontFamilyList.SelectedItem = cbi;
                        this.tvOutLine.FontFamily = fli.FontFamily;
                        break;
                    }
                }
            }

            this.fontFamilyList.SelectionChanged += fontFamilyList_SelectionChanged;

            //载入格式化动作何时执行的设置
            var format = App.ConfigManager.Get("AutoFormat");
            if (string.IsNullOrEmpty(format) == false && (format.ToLower() == "true"))
                miFormatBeforeSave.IsChecked = true;
            else
                miFormatBeforeSave.IsChecked = false;

            //启动时不显示工作区选择器（而是直接打开上次记录的工作区）
            var selectWorkspaceAtStart = App.ConfigManager.Get("SelectWorkspaceAtStart");
            if (string.IsNullOrEmpty(selectWorkspaceAtStart) == false)
            {
                bool dsws;
                if (bool.TryParse(selectWorkspaceAtStart, out dsws))
                {
                    this.SelectWorkspaceAtStart = dsws;
                }
            }
            miSelectWorkspaceAtStart.IsChecked = this.SelectWorkspaceAtStart;

            helpFrame.Source = new Uri(Globals.DefaultWorkspacePath + "Help~.html");//不能加此前缀"file:///" + 

            //读取Html Help Workshop.exe的路径。
            if (File.Exists(Globals.hhwInstalledPath) == false)
            {
                //为什么要这样做呢？
                //这是因为版权问题，不便集成Html Help Workshop到我的安装包中。
                //但问题在于，从网上下载的各个版本（哪怕是微软官方网站下载的英文版），在独立状态下都很容易出错。
                //所以还是集成了。这样，原先分离的做法就不想作用了。
                //如果将来还可以分离，这段代码就又有用了。所以未删除。
                var hhwInstalledPath = App.ConfigManager.Get("HHWInstalledPath");
                if (File.Exists(hhwInstalledPath))
                {
                    this.hhwInstalledPath = hhwInstalledPath;
                    var directoryPath = Path.GetDirectoryName(hhwInstalledPath);
                    if (directoryPath.EndsWith("\\") == false)
                        directoryPath += "\\";
                    this.hhcInstalledPath = directoryPath + "hhc.exe";
                }
                else
                {
                    //尝试从注册表中读取安装的hhw.exe的路径
                    RegistryKey regSubKey;
                    RegistryKey regKey = Registry.LocalMachine;
                    string strRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\hhw.exe";
                    regSubKey = regKey.OpenSubKey(strRegPath);
                    if (regSubKey != null)
                    {
                        string pathValue = regSubKey.GetValue("").ToString();
                        if (File.Exists(pathValue))
                        {
                            this.hhwInstalledPath = pathValue;
                            var directoryPath = Path.GetDirectoryName(pathValue);
                            if (directoryPath.EndsWith("\\") == false)
                                directoryPath += "\\";
                            this.hhcInstalledPath = directoryPath + "hhc.exe";
                        }
                    }
                }
            }

            //是否开启自动完成
            var isAutoCompletionEnabledText = App.ConfigManager.Get("IsAutoCompletionEnabled");
            if (string.IsNullOrEmpty(isAutoCompletionEnabledText) == false)
            {
                bool ac;
                if (bool.TryParse(isAutoCompletionEnabledText, out ac))
                {
                    this.IsAutoCompletionEnabled = ac;
                }
            }
            else
            {
                this.IsAutoCompletionEnabled = false;//默认关闭，这玩意儿还是很麻烦的。
            }
            miIsAutoCompletionEnabled.IsChecked = this.IsAutoCompletionEnabled;

            //是否开启试题编辑功能
            var isExamEnabledText = App.ConfigManager.Get("IsExamEnabled");
            if (string.IsNullOrEmpty(isExamEnabledText) == false)
            {
                bool r;
                if (bool.TryParse(isExamEnabledText, out r))
                {
                    this.IsExamEnabled = r;
                }
            }
            else
            {
                this.IsExamEnabled = false;//默认关闭
            }
            miIsExamEnabled.IsChecked = this.IsExamEnabled && this.IsAutoCompletionEnabled;

            //是否开启英译中词典功能
            var isEnToChineseDictEnabledText = App.ConfigManager.Get("IsEnToChineseDictEnabled");
            if (string.IsNullOrEmpty(isEnToChineseDictEnabledText) == false)
            {
                bool ecde;
                if (bool.TryParse(isEnToChineseDictEnabledText, out ecde))
                {
                    this.IsEnToChineseDictEnabled = ecde;
                }
            }
            else
            {
                this.isEnToChineseDictEnabled = false;//默认关闭
            }
            miIsEnToChineseDictEnabled.IsChecked = this.IsEnToChineseDictEnabled;

            var isPopupContextToolbarEnabledText = App.ConfigManager.Get("IsPopupContextToolbarEnabled");
            if (string.IsNullOrWhiteSpace(isPopupContextToolbarEnabledText) == false)
            {
                bool ipct;
                if (bool.TryParse(isPopupContextToolbarEnabledText, out ipct))
                {
                    this.isPopupContextToolbarEnabled = ipct;
                }
            }
            RefreshIsPopupContextToolbarEnabled(this.IsPopupContextToolbarEnabled);

            //是否显示空格占位符
            var isShowSpacesText = App.ConfigManager.Get("IsShowSpaces");
            if (string.IsNullOrEmpty(isShowSpacesText) == false)
            {
                bool ss;
                if (bool.TryParse(isShowSpacesText, out ss))
                {
                    this.IsShowSpaces = ss;
                }
            }
            else
            {
                this.isShowSpaces = false;//默认关闭
            }
            miShowSpaces.IsChecked = this.IsShowSpaces;

            //是否显示段落标记
            var isShowEndOfLineText = App.ConfigManager.Get("IsShowEndOfLine");
            if (string.IsNullOrEmpty(isShowEndOfLineText) == false)
            {
                bool sel;
                if (bool.TryParse(isShowEndOfLineText, out sel))
                {
                    this.IsShowEndOfLine = sel;
                }
            }
            else
            {
                this.isShowEndOfLine = false;//默认关闭
            }
            miShowEndOfLine.IsChecked = this.IsShowEndOfLine;

            //是否显示制表符
            var isShowTabsText = App.ConfigManager.Get("IsShowTabs");
            if (string.IsNullOrEmpty(isShowTabsText) == false)
            {
                bool st;
                if (bool.TryParse(isShowTabsText, out st))
                {
                    this.IsShowTabs = st;
                }
            }
            else
            {
                this.isShowTabs = false;//默认关闭
            }
            miShowTabs.IsChecked = this.IsShowTabs;

            //是否显示行号
            var isShowLinesNumbersText = App.ConfigManager.Get("IsShowLinesNumbers");
            if (string.IsNullOrEmpty(isShowLinesNumbersText) == false)
            {
                bool sln;
                if (bool.TryParse(isShowLinesNumbersText, out sln))
                {
                    this.IsShowLineNumbers = sln;
                }
            }
            else
            {
                this.IsShowLineNumbers = true;//默认开启
            }
            miShowLineNumbers.IsChecked = this.IsShowLineNumbers;

            //载入VimKey
            var vimKeyText = App.ConfigManager.Get("VimKey");
            if (string.IsNullOrWhiteSpace(vimKeyText))
            {
                Globals.VimKey = Key.None;
                tbVimKeyText.Text = "None";
            }
            else
            {
                switch (vimKeyText)
                {
                    case "LeftCtrl":
                        {
                            Globals.VimKey = Key.LeftCtrl;
                            tbVimKeyText.Text = "LCtrl";
                            break;
                        }
                    case "RightCtrl":
                        {
                            Globals.VimKey = Key.RightCtrl;
                            tbVimKeyText.Text = "RCtrl";
                            break;
                        }
                    case "RightShift":
                        {
                            Globals.VimKey = Key.RightShift;
                            tbVimKeyText.Text = "RShift";
                            break;
                        }
                    case "LeftShift":
                        {
                            Globals.VimKey = Key.LeftShift;
                            tbVimKeyText.Text = "LShift";
                            break;
                        }
                    default:
                        {
                            Globals.VimKey = Key.None;
                            tbVimKeyText.Text = "None";
                            break;
                        }
                }
            }

            //在工作区管理器中是否尝试显示标题而非文件短名
            var showTitleInWorkspaceManagerText = App.ConfigManager.Get("ShowTitleInWorkspaceManager");
            if (string.IsNullOrWhiteSpace(showTitleInWorkspaceManagerText) == false)
            {
                bool stiwm;
                if (bool.TryParse(showTitleInWorkspaceManagerText, out stiwm))
                {
                    this.ShowTitleInWorkspaceManager = stiwm;
                }
            }
            else
            {
                this.showTitleInWorkspaceManager = true;//默认开启
            }
            ckxShowTitle.IsChecked =
            miShowTitleInWorkspaceManager.IsChecked = this.ShowTitleInWorkspaceManager;

            //在文字表区域按 Tab 键是否先选中所在单元格中的所有文本
            var selectCellFirst = App.ConfigManager.Get("SelectCellFirst");
            if (string.IsNullOrEmpty(selectCellFirst) == false)
            {
                bool se;
                if (bool.TryParse(selectCellFirst, out se))
                {
                    this.SelectCellFirst = se;
                }
            }
            miSelectCellFirst.IsChecked = this.SelectCellFirst;

            //TextAutoWrap，文本自动折行不宜记忆。2019年3月23日

            //是否点击“关闭”按钮时最小化到托盘图标而不是关闭程序
            var isCloseToIconText = App.ConfigManager.Get("CloseToIcon");
            if (string.IsNullOrWhiteSpace(isCloseToIconText) == false)
            {
                this.isCloseToIcon = bool.Parse(isCloseToIconText);
            }
            miCloseToIcon.IsChecked = this.isCloseToIcon;

            var supportFoldingText = App.ConfigManager.Get("SupportFolding");
            if (string.IsNullOrWhiteSpace(supportFoldingText) == false)
            {
                this.supportFolding = bool.Parse(supportFoldingText);
            }
            miFoldingSwitch.IsChecked = this.supportFolding;  // 默认关闭折叠支持。
            if (this.supportFolding)
            {
                spFolding.Visibility =
                    miFolding.Visibility = Visibility.Visible;
            }
            else
            {
                spFolding.Visibility =
                    miFolding.Visibility = Visibility.Collapsed;
            }

            //搜索范围
            var searchArea = App.ConfigManager.Get("SearchRange");
            if (string.IsNullOrWhiteSpace(searchArea) == false)
            {
                foreach (var item in cmbSearchArea.Items)
                {
                    if (!(item is ComboBoxItem ci)) continue;

                    if ((ci.Tag as string) == searchArea)
                    {
                        cmbSearchArea.SelectedItem = ci;
                    }
                }
            }

            //窗口状态
            var windowStateText = App.ConfigManager.Get("WindowState");
            if (string.IsNullOrWhiteSpace(windowStateText) == false)
            {
                switch (windowStateText)
                {
                    case "Maximized":
                        {
                            this.WindowState = WindowState.Maximized;
                            break;
                        }
                    default:
                        {
                            this.WindowState = WindowState.Normal;
                            break;
                        }
                }
            }

            //文本尺寸
            var fontSizeText = App.ConfigManager.Get("FontSize");
            if (string.IsNullOrWhiteSpace(fontSizeText) == false)
            {
                double newFontSize;
                if (double.TryParse(fontSizeText, out newFontSize))
                {
                    this.mainTabControl.FontSize = newFontSize;
                    RefreshFontSize();
                }
            }

            //设置高亮显示方案，性能低时选择简单方案，计算机性能强大时选择复杂方案
            var highlightingSettingText = App.ConfigManager.Get("HighlightingSetting");
            if (string.IsNullOrWhiteSpace(highlightingSettingText) == false)
            {
                MarkdownEditorBase.HighlightingType highlight;
                if (Enum.TryParse<MarkdownEditorBase.HighlightingType>(highlightingSettingText, out highlight))
                {
                    this.HighlightingSetting = highlight;
                }
            }

            switch (this.HighlightingSetting)
            {
                case MarkdownEditorBase.HighlightingType.None:
                    {
                        miHighlightingNone.IsChecked = true;
                        miHighlightingAdvance.IsChecked = false;
                        miHighlightingOnlyHeaders.IsChecked = false;
                        break;
                    }
                case MarkdownEditorBase.HighlightingType.OnlyHeaders:
                    {
                        miHighlightingNone.IsChecked = false;
                        miHighlightingAdvance.IsChecked = false;
                        miHighlightingOnlyHeaders.IsChecked = true;
                        break;
                    }
                default:
                    {
                        miHighlightingNone.IsChecked = false;
                        miHighlightingAdvance.IsChecked = true;
                        miHighlightingOnlyHeaders.IsChecked = false;
                        break;
                    }
            }
            //考虑到某些选项必须先载入，所以最后载入上一次打开的那些文档。
            var rememberOpenedFilesConfig = App.ConfigManager.Get("RememberOpenedFiles");
            if (string.IsNullOrEmpty(rememberOpenedFilesConfig) == false)
            {
                bool r;
                if (bool.TryParse(rememberOpenedFilesConfig, out r))
                {
                    this.RememberOpenedFiles = r;
                }
            }
            else
            {
                this.RememberOpenedFiles = true;//默认打开
            }
            miRememberOpenedFiles.IsChecked = this.RememberOpenedFiles;

            var alwaysCompileBeforePreviewConfig = App.ConfigManager.Get("AlwaysCompileBeforePreview");
            if (string.IsNullOrEmpty(alwaysCompileBeforePreviewConfig) == false)
            {
                bool acbpc;
                if (bool.TryParse(alwaysCompileBeforePreviewConfig, out acbpc))
                {
                    this.AlwaysCompileBeforePreview = acbpc;
                }
            }
            else
            {
                this.AlwaysCompileBeforePreview = false;
            }
            miAlwaysCompileBeforePreview.IsChecked = this.AlwaysCompileBeforePreview;

            var leftWrapTextConfig = App.ConfigManager.Get("LeftWrapText");
            if (string.IsNullOrEmpty(leftWrapTextConfig) == false)
            {
                this.leftWrapText = leftWrapTextConfig;
            }
            else this.leftWrapText = "`";

            var rightWrapTextConfig = App.ConfigManager.Get("RightWrapText");
            if (string.IsNullOrEmpty(rightWrapTextConfig) == false)
            {
                this.rightWrapText = rightWrapTextConfig;
            }
            else this.rightWrapText = "";
        }

        private string leftWrapText = "`";
        /// <summary>
        /// 适用于按下 ` 键时自动在选中文本左侧插入用于“包装”的文本，一般适合使用左括号 （[【 等字符。默认情况下直接输入 ` 字符。
        /// 当此属性值为空时，默认输入 ` 字符。
        /// </summary>
        public string LeftWrapText
        {
            get
            {
                if (string.IsNullOrEmpty(leftWrapText))
                    return "`";
                return leftWrapText;
            }
            set { leftWrapText = value; }
        }

        private string rightWrapText = "";
        /// <summary>
        /// 适用于按下 ` 键时自动在选中文本右侧插入用于“包装”的文本，一般适合用右括号 ）]】 等字符。默认情况下为空——配合默认情况下左侧的 ` 字符。
        /// </summary>
        public string RightWrapText
        {
            get
            {
                if (string.IsNullOrEmpty(rightWrapText))
                    return "";
                return rightWrapText;
            }
            set { rightWrapText = value; }
        }

        /// <summary>
        /// 编译后的 Html 使用的主题配色的标记文本。
        /// </summary>
        public string ThemeText
        {
            get
            {
                if (cmbColor.SelectedItem == null)
                {
                    cmbColor.SelectedIndex = 0;
                    return "light";//默认
                }

                return (cmbColor.SelectedItem as ComboBoxItem).Tag.ToString();
            }
        }

        /// <summary>
        /// 用于刷新树型框列表。
        /// </summary>
        private WorkspaceManager workspaceManager;
        /// <summary>
        /// 工作区管理器。
        /// </summary>
        internal WorkspaceManager WorkspaceManager
        {
            get
            {
                if (this.workspaceManager == null)
                {
                    this.workspaceManager = new WorkspaceManager(this);
                }
                return workspaceManager;
            }
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        public void Undo()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            if (!(this.mainTabControl.SelectedItem is MarkdownEditor eti)) return;

            try
            {
                if (eti.EditorBase.CanUndo)
                {
                    eti.EditorBase.Undo();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        public void Redo()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            if (!(this.mainTabControl.SelectedItem is MarkdownEditor eti)) return;

            try
            {
                if (eti.EditorBase.CanRedo)
                {
                    eti.EditorBase.Redo();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 保存当前活动编辑器中的文档。
        /// </summary>
        /// <param name="formatBeforeSave">保存前是否格式化 Markdown 文本。</param>
        private void SaveDocment(bool formatBeforeSave)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            if (!(this.mainTabControl.SelectedItem is MarkdownEditor eti)) return;

            if (eti.IsModified == false) return;

            string oldFileFullPath = eti.FullFilePath;

            if (formatBeforeSave)
            {
                //eti.EditorBase.Text = eti.GetFormatedMarkdownText();
                eti.EditorBase.Format();
            }

            if (File.Exists(eti.FullFilePath))
            {
                eti.SaveDocument();
            }
            else
            {
                SaveNewFile(ActivedEditor);
            }

            if (eti.FullFilePath.ToLower() == GetMetaFilePathOfDirectory(Globals.PathOfWorkspace).ToLower())
            {
                notifyIcon.Text =
                    this.Title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace) + " - " + Globals.AppName;
            }

            AnchorsManager.Load();

            //自打改走IDE路子以后，下面这段代码几乎已经不可能再起作用了。
            if (eti.FullFilePath != oldFileFullPath)
            {
                //this.workspaceManager.Refresh(eti.FullFilePath);//刷新工作区文件列表。
                var entryItem = FindWorkspaceTreeViewItem(eti.FullFilePath);
                if (entryItem != null)
                {
                    entryItem.RefreshFileState();
                }
            }
        }

        /// <summary>
        /// 仅用以保存不是从“工作区”创建的、且不是从磁盘打开的文件。
        /// </summary>
        private string SaveNewFile(MarkdownEditor editor)
        {
            if (editor == null) return "未指定目标编辑器。";

            WorkspaceTreeViewItem wtvi;
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先在主界面左侧工作区浏览窗口中选定一个目录来创建文件。" +
                    "如果看不到工作区浏览窗口，请按F12键显示左工具栏。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return "未选中保存目录。";
            }
            else
            {
                wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            }

            string directoryPath;
            WorkspaceTreeViewItem destWtvi;

            if (wtvi.IsMarkdownFilePath)
            {
                directoryPath = wtvi.ParentDirectory;
                destWtvi = wtvi.ParentWorkspaceTreeViewItem;
            }
            else
            {
                directoryPath = wtvi.FullPath;
                destWtvi = wtvi;
            }

            if (Directory.Exists(directoryPath) == false)
            {
                var lastIndex = directoryPath.LastIndexOf("\\");
                if (lastIndex < 0)
                {
                    LMessageBox.Show("只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return "指定位置不是目录。";
                }

                var parentDirectory = directoryPath.Substring(0, lastIndex);
                if (Directory.Exists(parentDirectory) == false)
                {
                    LMessageBox.Show("只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return "指定位置不是目录。";
                }

                directoryPath = parentDirectory;
            }

            if (directoryPath.EndsWith("~") || directoryPath.EndsWith("~\\"))
            {
                LMessageBox.Show("以波形符结尾的目录是程序自动添加的资源目录，不能在其中再建立MarkDown文件。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return "指定位置不是合法目录。";
            }

            if (directoryPath.EndsWith("\\") == false) directoryPath += "\\";

            string newShortFileName = InputBox.Show(Globals.AppName, "请输入新文件名（不能以“_”开头）：", "", true,
                   "说明：\r\n　　⑴不需要输入后缀名；\r\n　　⑵请尽可能设定有意义的文件名。\r\n　　⑶不能以下划线开头。\r\n　　⑷尽量以字母开头。\r\n　　因为此文件名很可能将来被其它文件引用。所以，如非必要，创建之后请勿随意更改文件名。");

            if (newShortFileName == null) return "用户放弃保存文件。";//用户放弃新建文件。

            if (newShortFileName.Contains(".") == false) newShortFileName += ".md";

            if (string.IsNullOrEmpty(newShortFileName) || newShortFileName.StartsWith("."))
            {
                LMessageBox.Show("文件名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return "用户未提供文件名。";
            }

            var newFilePath = directoryPath + newShortFileName;
            if (File.Exists(newFilePath))
            {
                LMessageBox.Show("已存在同名文件，无法完成操作！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return "已存在同名文件！";
            }

            try
            {
                var docTitle = newShortFileName.EndsWith(".md") ? newShortFileName.Substring(0, newShortFileName.Length - 3) : newShortFileName;
                editor.EditorBase.Save(newFilePath);
                editor.FullFilePath = newFilePath;

                WorkspaceTreeViewItem newtvi = new WorkspaceTreeViewItem(newFilePath, Globals.MainWindow);
                destWtvi.Items.Insert(0, newtvi);
                newtvi.IsSelected = true;

                return string.Empty;
            }
            catch (Exception ex)
            {
                LMessageBox.Show("新建文件失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return ex.Message;
            }
        }

        /// <summary>
        /// 保存当前打开的所有文档。
        /// </summary>
        private void SaveAllDocumentsAndOptions()
        {
            //啰嗦
            //var result = LMessageBox.Show("此操作除保存已修改的文档之外，还会将当前工作区 Html 编译选项和工作区条目状态存盘。\r\n\r\n　　要继续吗？",
            //    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            //if (result != MessageBoxResult.Yes) return;

            //TODO: 按下 SaveAll 时，暂时只想到要保存这些东西，以后可以继续添加。
            RememberWorkspaceHtmlCompileOptions();
            WorkspaceManager.SaveWorkspaceTreeviewToXml();

            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                if (item is MarkdownEditor eti && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count > 0)
            {
                string saveInfo;
                bool needRefreshWindowTitle = false;
                string workspaceMetaFilePath = GetMetaFilePathOfDirectory(Globals.PathOfWorkspace).ToLower();
                foreach (MarkdownEditor eti in needSavingDocumentList)
                {
                    if (miFormatBeforeSave.IsChecked)
                    {
                        eti.EditorBase.Text = eti.FormatedMarkdownText();
                    }

                    if (eti.FullFilePath.ToLower() == workspaceMetaFilePath)
                    {
                        needRefreshWindowTitle = true;
                    }

                    saveInfo = eti.SaveDocument();
                    //this.workspaceManager.Refresh(eti.FullFilePath);//刷新工作区文件列表。
                    var entryItem = FindWorkspaceTreeViewItem(eti.FullFilePath);
                    if (entryItem != null)
                    {
                        entryItem.RefreshFileState();
                    }

                    if (saveInfo == "用户取消操作")
                    {
                        LMessageBox.Show("取消了操作。未能保存所有需要保存的文档。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else if (saveInfo != string.Empty)
                    {
                        LMessageBox.Show("未能保存所有需要保存的文件。错误消息如下：\r\n" + saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;//不关闭
                    }
                    else
                    {
                        AnchorsManager.Load();
                    }
                }

                if (needRefreshWindowTitle)
                {
                    notifyIcon.Text =
                        this.Title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace) + " - " + Globals.AppName;
                }
            }

            string shouldSelItemPath = null;
            if (tvWorkDirectory.SelectedItem is WorkspaceTreeViewItem wi)
            {
                shouldSelItemPath = wi.FullPath;
            }

            AnchorsManager.Load();

            //这个办法效率太低。
            //this.workspaceManager.Refresh(shouldSelItemPath);//刷新整个工作区文件列表。
        }

        /// <summary>
        /// 关闭程序前做些清理、记录工作。
        /// </summary>
        void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (isForceExit == false)
            {
                if (this.IsCloseToIcon)
                {
                    windowState = this.WindowState;
                    this.Hide();
                    e.Cancel = true;
                    return;
                }
            }

            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                if (item is MarkdownEditor eti && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count > 0)
            {
                MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                    Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
                switch (r)
                {
                    case MessageBoxResult.Yes:
                        {
                            string saveInfo;
                            foreach (MarkdownEditor eti in needSavingDocumentList)
                            {
                                saveInfo = eti.SaveDocument();
                                if (saveInfo == "用户取消操作")
                                {
                                    e.Cancel = true;
                                    isForceExit = false;//还原
                                    return;//不关闭
                                }
                                else if (saveInfo != string.Empty)
                                {
                                    LMessageBox.Show(saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                    e.Cancel = true;
                                    isForceExit = false;//还原
                                    return;//不关闭
                                }
                                else { AnchorsManager.Load(); }
                            }

                            break;
                        }
                    case MessageBoxResult.Cancel:
                        {
                            e.Cancel = true;
                            isForceExit = false;//还原
                            return;
                        }
                        //default://No,直接关闭窗口
                }
            }

            RememberOpenedFilesForWorkspace();

            App.ConfigManager.Set("SearchRange", (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString());
            App.ConfigManager.Set("WindowState", this.WindowState.ToString());

            //[废弃]记录工作区管理器中各项的“展开/折叠”状态。已由WorkspaceItems.xml文件接管。
            //SaveWorkspaceCollapseStatus();

            //退出托盘图标
            notifyIcon?.Dispose();

            //记录到Xml文件中下次打开会快一些。
            workspaceManager.SaveWorkspaceTreeviewToXml();

        }

        //[废弃]记录工作区管理器中各项的“展开/折叠”状态。已由WorkspaceItems.xml文件接管。
        //private void SaveWorkspaceCollapseStatus()
        //{
        //    StringBuilder sbuilder = new StringBuilder();
        //    foreach (var item in tvWorkDirectory.Items)
        //    {
        //        var wi = item as WorkspaceTreeViewItem;
        //        if (wi == null) continue;

        //        RememberWorkspaceItemsCollapseStatus(wi, ref sbuilder);
        //    }
        //    App.WorkspaceConfigManager.Set("WorkspaceCollapseStatus", sbuilder.ToString());
        //}

        private void RememberWorkspaceItemsCollapseStatus(WorkspaceTreeViewItem item, ref StringBuilder sbuilder)
        {
            if (item == null) return;

            var fullPath = item.FullPath.EndsWith("\\") ? item.FullPath : (item.FullPath + "\\");

            sbuilder.Append($"{fullPath}|{item.IsExpanded.ToString()}||");

            foreach (var subItem in item.Items)
            {
                if (!(subItem is WorkspaceTreeViewItem subwi)) continue;

                RememberWorkspaceItemsCollapseStatus(subwi, ref sbuilder);
            }
        }

        /// <summary>
        /// 记录所有当前打开的文档的路径，以便下次启动程序时自动打开。
        /// </summary>
        private void RememberOpenedFilesForWorkspace()
        {
            if (this.RememberOpenedFiles)
            {
                //重置工作区配置文件管理器
                App.WorkspaceConfigManager = null;

                StringBuilder sb = new StringBuilder();
                foreach (var item in this.mainTabControl.Items)
                {
                    if (!(item is MarkdownEditor me) || File.Exists(me.FullFilePath) == false) continue;

                    sb.Append(me.FullFilePath);
                    sb.Append(";");
                }

                App.WorkspaceConfigManager.Set("OpenedFiles", sb.ToString());

                if (this.mainTabControl.SelectedItem == null)
                {
                    App.WorkspaceConfigManager.Set("ActiveDocumentFullPath", "");
                }
                else
                {
                    if (this.mainTabControl.SelectedItem is MarkdownEditor editor)
                    {
                        App.WorkspaceConfigManager.Set("ActiveDocumentFullPath", editor.FullFilePath);
                    }
                    else
                    {
                        App.WorkspaceConfigManager.Set("ActiveDocumentFullPath", "");
                    }
                }
            }
        }

        /// <summary>
        /// 处理不会在工作区管理器和编辑器操作之间产生冲突的快捷键。
        /// 处理可能存在冲突，但是工作区管理器不需要使用或需要屏蔽其功能的快捷键。
        /// </summary>
        void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
            KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
            KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
            KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
            KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
            KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

            bool isCtrl, isShift, isAlt;

            isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
            isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
            isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;
            switch (e.Key)
            {
                case Key.Delete:
                    {
                        if (isCtrl && isShift && !isAlt)
                        {
                            miDeleteFile_Click(sender, e);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.N:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            NewFile(false);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.T:
                    {
                        if (isCtrl)
                        {
                            if (isShift)//Ctrl+Shift+T
                            {
                                FormatAsTextTable();
                                e.Handled = true;//否则会插入一个字母t
                            }
                            else
                            {
                                if (isAlt == false)//Ctrl+T
                                {
                                    NewFile(false);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.S:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                SaveAllDocumentsAndOptions();
                                e.Handled = true;
                            }
                            else
                            {
                                SaveDocment(miFormatBeforeSave.IsChecked);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.O:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            miOpenDocument_Click(sender, e);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.OemPlus:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                FontSizeUp();
                                e.Handled = true;
                            }
                            else
                            {
                                if (this.mainTabControl.SelectedItem is MarkdownEditor selEditor)
                                {
                                    selEditor.EditorBase.FindNextHeader();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.OemMinus:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                FontSizeDown();
                                e.Handled = true;
                            }
                            else
                            {
                                var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (selEditor != null)
                                {
                                    selEditor.EditorBase.FindPreviewHeader();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D0:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            this.mainTabControl.FontSize = 16;//默认字号16。
                            App.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                            RefreshFontSize();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F1:
                    {
                        if (!isAlt)
                        {
                            if (!isShift)
                            {
                                if (isCtrl)
                                {
                                    btnResetLeftToolbarLayout_MouseLeftButtonDown(null, null);
                                    e.Handled = true;
                                }
                                else
                                {
                                    //切换透视图
                                    if (cmbPerspective.IsDropDownOpen)
                                    {
                                        cmbPerspective.SelectedIndex = 0;
                                        cmbPerspective.IsDropDownOpen = false;
                                        var activeEditor = ActivedEditor;
                                        if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                        e.Handled = true;
                                        break;
                                    }

                                    if (cmbPerspective.SelectedIndex != (int)Perspective.VerticalMode &&
                                        cmbPerspective.SelectedIndex != (int)Perspective.FullScreenPreview &&
                                        cmbPerspective.SelectedIndex != (int)Perspective.MiniMode)
                                    {
                                        SwitchLeftToolBarToggle();
                                        e.Handled = true;
                                    }
                                }
                            }
                            else
                            {
                                // Shift+F1 编辑选择题
                                miEditChoiceQuestions_Click(sender, e);
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (isCtrl)//Ctrl+Alt+F1显示帮助
                            {
                                ShowHelp();
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.F2:
                    {
                        if (!isCtrl && !isShift && !isAlt)
                        {
                            //切换透视图
                            if (cmbPerspective.IsDropDownOpen)
                            {
                                cmbPerspective.SelectedIndex = 1;
                                cmbPerspective.IsDropDownOpen = false;
                                var activeEditor = ActivedEditor;
                                if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.U:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                WrapWithSTag();
                            }
                            else
                            {
                                WrapWithUTag();
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F4:
                    {
                        if (!isShift && !isCtrl && !isAlt)
                        {
                            if (cmbPerspective.IsDropDownOpen)
                            {
                                cmbPerspective.SelectedIndex = 3;
                                cmbPerspective.IsDropDownOpen = false;
                                var activeEditor = ActivedEditor;
                                if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.F5:
                    {
                        if (!isCtrl)
                        {
                            if (!isAlt)
                            {
                                if (isShift)
                                {
                                    cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
                                }
                                else
                                {
                                    if (cmbPerspective.IsDropDownOpen)  //切换透视图
                                    {
                                        cmbPerspective.SelectedIndex = 4;
                                        cmbPerspective.IsDropDownOpen = false;
                                        var activeEditor = ActivedEditor;
                                        if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                        e.Handled = true;
                                        break;
                                    }
                                }

                                CompileAndPreviewHtml();
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (!isAlt)
                            {
                                if (!isShift)
                                {
                                    CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.ByDocument);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.F9:
                    {
                        if (!isCtrl && !isAlt && !isShift)
                        {
                            if (cmbPerspective.IsDropDownOpen)
                            {
                                cmbPerspective.SelectedIndex = (int)Perspective.MiniMode;             //切换透视图到迷你模式
                                cmbPerspective.IsDropDownOpen = false;

                                var activeEditor = ActivedEditor;
                                if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                e.Handled = true;
                                break;
                            }

                            MainTabControl_ItemListButtonClicked(sender, null);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F11:
                    {
                        if (isCtrl)
                        {
                            // 用快捷键调用 Python 脚本。
                            RunPythonScriptsByShortCuts();
                            e.Handled = true;
                        }
                        else
                        {
                            if (!isAlt && !isShift)
                            {
                                cmbPerspective.IsDropDownOpen = !cmbPerspective.IsDropDownOpen;
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.F12:
                    {
                        if (!isCtrl)
                        {
                            if (!isAlt)
                            {
                                if (!isShift)
                                {
                                    // 退出全屏预览可以改用 Esc
                                    //if (cmbPerspective.SelectedIndex == (int)Perspective.FullScreenPreview)
                                    //{
                                    //    cmbPerspective.SelectedIndex = (int)Perspective.Normal;//退出全屏预览
                                    //}
                                    //else SwitchRightToolBarToggle();
                                    if (cmbPerspective.IsDropDownOpen)
                                    {
                                        cmbPerspective.SelectedIndex = (int)Perspective.VerticalMode;             //切换透视图到纵向模式
                                        cmbPerspective.IsDropDownOpen = false;

                                        var activeEditor = ActivedEditor;
                                        if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                        break;
                                    }
                                    else
                                    {
                                        if (cmbPerspective.SelectedIndex != (int)Perspective.VerticalMode &&
                                        cmbPerspective.SelectedIndex != (int)Perspective.FullScreenPreview &&
                                        cmbPerspective.SelectedIndex != (int)Perspective.MiniMode)
                                        {
                                            SwitchRightToolBarToggle();
                                        }
                                    }
                                }
                                else
                                {
                                    cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
                                }
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (!isShift && !isAlt)
                            {
                                //Ctrl+F12
                                miPreviewWholeDocument_Click(sender, e);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.D:
                    {
                        var ae = ActivedEditor;
                        if (isShift && isCtrl && !isAlt)
                        {
                            InsertDateText();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.G:
                    {
                        if (isCtrl && !isShift && !isAlt)
                        {
                            //还是给个窗口让用户选择是跳转到：
                            //链接（网址 图像链接 Markdown链接）/标题/TODO标记/任务列表

                            ContextMenu jumpContextMenu = new ContextMenu()
                            {
                                FontFamily = this.FontFamily,
                                FontSize = 14,
                                SnapsToDevicePixels = true,
                                Style = TryFindResource("MetroContextMenu") as Style,
                            };
                            TextOptions.SetTextFormattingMode(jumpContextMenu, TextFormattingMode.Display);

                            //ControlTemplate SubMenuItemControlTemplate = Globals.MainWindow.TryFindResource("SubMenuItemControlTemplate") as ControlTemplate;
                            //ControlTemplate MidMenuItemControlTemplate = Globals.MainWindow.TryFindResource("MidMenuItemControlTemplate") as ControlTemplate;
                            //ControlTemplate MenuItemControlTemplate = Globals.MainWindow.TryFindResource("MenuItemControlTemplate") as ControlTemplate;

                            MenuItem mciPreviewImage = new MenuItem()
                            {
                                Header = "预览图像(_G)",
                                ToolTip = "在左工具栏图像预览区预览图像",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciPreviewImage.Click += MciPreviewImage_Click;
                            jumpContextMenu.Items.Add(mciPreviewImage);

                            MenuItem mciGotoLink = new MenuItem()
                            {
                                //[包含图像链接]
                                Header = "跳转到链接(_L)",
                                ToolTip = "包括 MD 文件、图像、网址等",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoLink.Click += MciGotoLink_Click;
                            jumpContextMenu.Items.Add(mciGotoLink);

                            MenuItem mciGotoHeader = new MenuItem()
                            {
                                Header = "跳转到标题(_H)",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoHeader.Click += MciGotoHeader_Click;
                            jumpContextMenu.Items.Add(mciGotoHeader);

                            MenuItem mciGotoTask = new MenuItem()
                            {
                                Header = "跳转到任务项(_T)",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoTask.Click += MciGotoTask_Click;
                            jumpContextMenu.Items.Add(mciGotoTask);

                            jumpContextMenu.Items.Add(new Separator());

                            MenuItem mciGotoTodoComment = new MenuItem()
                            {
                                Header = "跳转到 TODO 注释(_D)",
                                Tag = "TODO",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };

                            mciGotoTodoComment.Click += MciGotoTodoComment_Click;
                            jumpContextMenu.Items.Add(mciGotoTodoComment);

                            MenuItem mciGotoDoingComment = new MenuItem()
                            {
                                Header = "跳转到 DOING 注释(_I)",
                                Tag = "DOING",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoDoingComment.Click += MciGotoTodoComment_Click;//DOING/DONE也属于TODO型标签
                            jumpContextMenu.Items.Add(mciGotoDoingComment);

                            MenuItem mciGotoDoneComment = new MenuItem()
                            {
                                Header = "跳转到 DONE 注释(_N)",
                                Tag = "DONE",
                                Style = TryFindResource("MetroMenuItem") as Style,
                            };
                            mciGotoDoneComment.Click += MciGotoTodoComment_Click;//DOING/DONE也属于TODO型标签
                            jumpContextMenu.Items.Add(mciGotoDoneComment);

                            var edit = ActivedEditor;
                            if (edit != null)
                            {
                                var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
                                var lineText = edit.EditorBase.Document.GetText(line.Offset, line.Length);

                                if (CustomMarkdownSupport.IsTodoCommentLine(lineText))
                                {
                                    mciGotoTodoComment.Focus();
                                }
                                else if (CustomMarkdownSupport.IsDateLine(lineText) ||
                                   CustomMarkdownSupport.IsTaskLine(lineText))
                                {
                                    mciGotoTask.Focus();
                                }
                                else
                                {

                                }
                            }

                            jumpContextMenu.Placement = PlacementMode.Center;
                            jumpContextMenu.PlacementTarget = this;
                            jumpContextMenu.IsOpen = true;

                            e.Handled = true;
                        }
                        break;
                    }
                case Key.E:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                miIsAutoCompletionEnabled_Clicked(sender, e);
                                e.Handled = true;
                            }
                            else
                            {
                                miIsExamEnabled_Click(sender, e);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.R:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                miWrapWithRegionMark_Click(sender, e);
                                e.Handled = true;
                            }
                            //2016年11月25日，将Ctrl+R改到各编辑器自身临时切换，不再统一切换。
                            //else
                            //{
                            //    miTextWrap_Click(sender, e);
                            //}
                        }
                        break;
                    }
                case Key.J:
                    {
                        if (isAlt)
                        {
                            if (isCtrl && !isShift)
                            {
                                miInsertAnchorLinkMark_Click(sender, e);
                                e.Handled = true;
                            }
                        }
                        else
                        {
                            if (isCtrl)
                            {
                                if (isShift)
                                {
                                    miInsertAnchorAtPreviewLine_Click(sender, e);
                                    e.Handled = true;
                                }
                                else
                                {
                                    miInsertAnchor_Click(sender, e);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.K:
                    {
                        if (isCtrl && !isAlt && !isShift)
                        {
                            miInsertLinkMark_Click(sender, e);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F:
                    {
                        if (isCtrl)
                        {
                            if (isShift)
                            {
                                if (isAlt == false)
                                {
                                    //Ctrl+Shift+F
                                    Format();
                                }
                                e.Handled = true;
                            }
                            else
                            {
                                if (isAlt)
                                {
                                    //Ctrl+Alt+F
                                    if (rdFindAndReplace.ActualHeight <= 20)
                                    {
                                        rdFindAndReplace.Height = new GridLength(0, GridUnitType.Auto);
                                        cmbFindText.UpdateLayout();
                                    }

                                    var editor = ActivedEditor;
                                    if (editor != null && editor.EditorBase.SelectedText.Contains("\r") == false &&
                                        editor.EditorBase.SelectedText.Contains("\n") == false && editor.EditorBase.SelectionLength > 0)
                                    {
                                        cmbFindText.Text = editor.EditorBase.SelectedText;
                                    }
                                    //else        //这个容易导致误操作
                                    //{
                                    //    cmbFindText.Text = "";
                                    //}

                                    cmbSearchArea.SelectedIndex = 1;

                                    cmbFindText.Focus();

                                    if (cmbFindText.Text.Length > 0)
                                    {
                                        FindText(tvFindAndReplace);
                                    }

                                    cmbFindText.Focus();

                                    if (cdRightToolsArea.ActualWidth < 100)
                                    {
                                        cdMainEditArea.Width =
                                            cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                                    }

                                    if (tcRightToolBar.SelectedIndex != 1)
                                    {
                                        tcRightToolBar.SelectedIndex = 1;
                                    }

                                    e.Handled = true;
                                }
                                //else
                                //{
                                //    //Ctrl+F
                                //    //如果放在这里，会导致在Html预览区域按“Ctrl+F”无法弹出查找框。
                                //    //所以这个功能必须放在编辑区中才好。
                                //}
                            }
                        }
                        break;
                    }
                case Key.H:
                    {
                        // Ctrl+H
                        if (isCtrl && !isAlt && !isShift)
                        {
                            if (QuickReplace())
                            {
                                e.Handled = true;
                                break;
                            }

                            if (rdFindAndReplace.ActualHeight <= 40)
                            {
                                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                                cmbFindText.UpdateLayout();
                            }

                            var editor = ActivedEditor;
                            if (editor != null && editor.EditorBase.SelectedText.Contains("\r") == false &&
                                editor.EditorBase.SelectedText.Contains("\n") == false && editor.EditorBase.SelectionLength > 0)
                            {
                                cmbFindText.Text = editor.EditorBase.SelectedText;
                            }
                            //else        //这个容易导致误操作
                            //{
                            //    cmbFindText.Text = "";
                            //}

                            cmbSearchArea.SelectedIndex = 0;

                            if (cmbFindText.Text.Length > 0)
                            {
                                cmbReplaceTextInputBox.Focus();
                            }
                            else
                            {
                                cmbFindText.Focus();
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.L:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            SelecetLineOrMoveToVerticalCenter(isShift);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F3:
                    {
                        if (isCtrl == false && isAlt == false)
                        {
                            if (isShift)
                            {
                                if (cmbFindText.Text.Length > 0)
                                {
                                    cbSearchUp.IsChecked = true;
                                    FindNext(cmbFindText.Text);
                                }
                            }
                            else
                            {
                                if (cmbPerspective.IsDropDownOpen)  //切换透视图
                                {
                                    cmbPerspective.SelectedIndex = 2;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                    break;
                                }

                                if (cmbFindText.Text.Length > 0)
                                {
                                    cbSearchUp.IsChecked = false;
                                    FindNext(cmbFindText.Text);
                                }
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.Escape:
                    {
                        if (isCtrl == false && isShift == false && isAlt == false)
                        {
                            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (efi != null) // && efi.EditorBase.IsSearchPanelOpened)
                            {
                                if (efi.EditorBase.CompletionWindow != null &&
                                    efi.EditorBase.CompletionWindow.Visibility == Visibility.Visible)
                                {
                                    efi.EditorBase.CompletionWindow.Close();
                                }

                                //CloseSearchPanel();//AvalonEdit的SearchPanel总是有些问题，放弃了
                                if (rdFindAndReplace.ActualHeight > 0)
                                {
                                    rdFindAndReplace.Height = new GridLength(0);
                                    FocusActiveEditor();
                                    e.Handled = true;
                                    break;
                                }
                            }

                            //之前这个键用于退出全屏状态
                            //但后来，发现经常在编辑时用到（例如关闭查找框、退出自动完成），
                            //一按就切换视图，不好。
                            switch (this.PerspectiveMode)
                            {
                                case Perspective.FullScreenPreview:
                                case Perspective.EditingAndPresentation:
                                    {
                                        cmbPerspective.SelectedIndex = (int)Perspective.Normal;
                                        e.Handled = true;
                                        break;
                                    }
                            }
                        }
                        break;
                    }
                case Key.OemTilde://波形符键（反引号键）
                    {
                        if (isCtrl)
                        {
                            if (!isAlt && !isShift)
                            {
                                ActivedEditor?.EditorBase.WrapTextWithAntiQuotes();
                            }
                        }
                        break;
                    }
                case Key.OemQuotes:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            ActivedEditor?.EditorBase.WrapTextWithQuotes(isShift);
                        }
                        break;
                    }
                case Key.OemPipe:
                    {
                        if (isCtrl && isShift && !isAlt)
                        {
                            //插入新列
                            miInsertNewColumn_Click(sender, e);
                        }
                        break;
                    }
                case Key.Q:
                    {
                        //定位到资源搜索框，这样就可以直接输入文本了。
                        if (isCtrl && !isShift && !isAlt)
                        {
                            cmbSearchResource.Focus();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F6:
                    {
                        if (!isCtrl && !isAlt)
                        {
                            if (!isShift)
                            {
                                if (cmbPerspective.IsDropDownOpen)  //切换透视图
                                {
                                    cmbPerspective.SelectedIndex = 5;
                                    cmbPerspective.IsDropDownOpen = false;
                                    var activeEditor = ActivedEditor;
                                    if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                    e.Handled = true;
                                    break;
                                }

                                //演示试题
                                PresentationExams();
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.F7:
                    {
                        if (!isCtrl && !isShift && !isAlt)
                        {
                            if (cmbPerspective.IsDropDownOpen)
                            {
                                cmbPerspective.SelectedIndex = 6;
                                cmbPerspective.IsDropDownOpen = false;
                                var activeEditor = ActivedEditor;
                                if (activeEditor != null) activeEditor.EditorBase.TextArea.Focus();
                                e.Handled = true;
                                break;
                            }
                            else
                            {
                                //自动格式化二维文字表
                                miFormatTextTable_Click(sender, e);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.F8:
                    {
                        if (!isCtrl && !isAlt && !isShift)
                        {
                            if (cmbPerspective.IsDropDownOpen)
                            {
                                cmbPerspective.SelectedIndex = 7;             //切换到对照模式
                                cmbPerspective.IsDropDownOpen = false;

                                //如果没有更改cmbPerspective.SelectedIndex，刷新对照区仍然是必要的
                                var activeEditor = ActivedEditor;
                                if (activeEditor != null)
                                {
                                    activeEditor.SendToRightCompareArea();
                                    activeEditor.EditorBase.TextArea.Focus();
                                }
                                e.Handled = true;
                                break;
                            }
                        }
                        break;
                    }
                case Key.D1:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 1);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    if (MoveToItemAnalys(1))
                                    {
                                        e.Handled = true;
                                        break;
                                    }

                                    //Ctrl+数字
                                    SwitchTitleLevel(1);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D2:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 2);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    if (MoveToItemAnalys(2))
                                    {
                                        e.Handled = true;
                                        break;
                                    }

                                    //Ctrl+数字
                                    SwitchTitleLevel(2);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D3:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 3);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    if (MoveToItemAnalys(3))
                                    {
                                        e.Handled = true;
                                        break;
                                    }

                                    //Ctrl+数字
                                    SwitchTitleLevel(3);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D4:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 4);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(4);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D5:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 5);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(5);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D6:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 6);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(6);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D7:
                    {
                        if (isCtrl)
                        {
                            if (!isShift)
                            {
                                if (isAlt)
                                {
                                    ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 7);
                                    e.Handled = true;
                                }
                                else
                                {
                                    miInsertHorizontalLine_Click(miInsertHorizontal, e);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D8:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 8);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.D9:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 9);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.Tab:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                if (mainTabControl.Items.Count > 1)
                                {
                                    if (mainTabControl.SelectedIndex == mainTabControl.Items.Count - 1)
                                    {
                                        mainTabControl.SelectedIndex = 0;
                                    }
                                    else
                                    {
                                        mainTabControl.SelectedIndex++;
                                    }

                                    var ae = ActiveEditor;
                                    if (ae != null)
                                    {
                                        ae.EditorBase.TextArea.Focus();
                                    }
                                    e.Handled = true;
                                }
                            }
                            else
                            {
                                if (mainTabControl.Items.Count > 1)
                                {
                                    if (mainTabControl.SelectedIndex == 0)
                                    {
                                        mainTabControl.SelectedIndex = mainTabControl.Items.Count - 1;
                                    }
                                    else
                                    {
                                        mainTabControl.SelectedIndex--;
                                    }

                                    var ae = ActiveEditor;
                                    if (ae != null)
                                    {
                                        ae.EditorBase.TextArea.Focus();
                                    }
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 用于在按下 Ctrl + H 组合键时，优先处理当前插入点位置前形如：  ;;replace_rule;;replacement  这样的字符串。
        /// 这个字符串用以向当前行行首位置进行替换。replace_rule 表示用于查找的规则，replacement 则是用于替换的文本。
        /// 
        /// 此功能的适用场景是：用户向后输入一小段文本后，发现前面有输入错误，又懒得脱离主输入区域去寻找方向键或用鼠标选取要更改的文本。
        /// </summary>
        /// <returns></returns>
        public static bool QuickReplace()
        {
            try
            {
                var ate = Globals.MainWindow.ActiveTextEditor;
                if (ate == null) return false;
                if (ate.SelectionLength > 0) return false;
                var line = ate.Document.GetLineByOffset(ate.SelectionStart);
                var lineText = ate.Document.GetText(line.Offset, line.Length);
                var preText = ate.Document.GetText(line.Offset, ate.SelectionStart - line.Offset);
                var tailText = lineText.Substring(preText.Length);

                if (string.IsNullOrWhiteSpace(preText)) return false;

                Regex reg = new Regex(@"[;；]{2,}.{1,}[;；]{2,}.{0,}", RegexOptions.RightToLeft | RegexOptions.Singleline);
                var match = reg.Match(preText);
                if (match.Success == false) return false;

                var pieces = Utils.Text.SplitToPiecesByRegex(@"[;；]{2,}", match.Value);
                string rule = null;
                string replacement = null;

                foreach (var piece in pieces)
                {
                    if (piece.IsMartchText) continue;
                    if (rule == null)
                    {
                        rule = piece.SourceText;
                        continue;
                    }

                    if (replacement == null)
                    {
                        replacement = piece.SourceText;
                        continue;
                    }
                }

                if (string.IsNullOrEmpty(rule)) return false;
                if (replacement == null) replacement = "";

                var prepreText = preText.Substring(0, preText.Length - match.Length);
                var replacedText = Regex.Replace(prepreText, rule, replacement);

                var result = replacedText + tailText;
                ate.Document.Replace(line.Offset, preText.Length, result);
                ate.Select(line.Offset + result.Length, 0);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 将选择题“解析”行中选定的文本移动到对应的错项的解析文本行。
        /// </summary>
        public bool MoveToItemAnalys(int v)
        {
            var ate = ActiveTextEditor;
            if (ate.SelectionLength <= 0) return false;

            var curLine = ate.Document.GetLineByOffset(ate.SelectionStart);
            var curLineText = ate.Document.GetText(curLine.Offset, curLine.Length);
            if (curLineText.StartsWith("　　解析＞＞") == false) return false;

            if (ate.SelectionStart < curLine.Offset + 6) return false;
            switch (v)
            {
                case 1: EditorPopupToolBar.MoveToFstItemAnalys(); break;
                case 2: EditorPopupToolBar.MoveToSecItemAnalys(); break;
                case 3: EditorPopupToolBar.MoveToTrdItemAnalys(); break;
                default: return false;
            }

            return true;
        }

        /// <summary>
        /// 调用被指定为用快捷键调用的脚本。
        /// </summary>
        internal void RunPythonScriptsByShortCuts()
        {
            var list = new List<UIElement>();
            foreach (var item in Globals.MainWindow.lbxPythonScripts.Items)
            {
                var ue = item as UIElement;
                if (ue != null) list.Add(ue);
            }

            foreach (var item in Globals.MainWindow.lbxWorkspacePythonScripts.Items)
            {
                var ue = item as UIElement;
                if (ue != null) list.Add(ue);
            }

            foreach (var item in list)
            {
                var psli = item as PythonScriptListItem;
                if (psli == null) continue;
                if (psli.CallMode != PythonScriptListItem.PythonScriptCallMode.RunByShortcuts) continue;

                PythonScriptListItem.RunScript(psli.CallMode, psli.FullPath, false);
            }
        }

        /// <summary>
        /// 查找所有 TODO Comment，以便跳转到某个 TODO Comment。
        /// （也可以是“DONE”、“DOING”。）
        /// </summary>
        private void MciGotoTodoComment_Click(object sender, RoutedEventArgs e)
        {
            var mark = (sender as MenuItem).Tag.ToString();
            GotoTodoComment(mark);
        }

        /// <summary>
        /// 查找所有任务列表，以便快速向某个任务列表跳转。
        /// </summary>
        private void MciGotoTask_Click(object sender, RoutedEventArgs e)
        {
            GotoTaskListItem();
        }

        /// <summary>
        /// 查找所有标题，以便快速向某个标题跳转。
        /// </summary>
        private void MciGotoHeader_Click(object sender, RoutedEventArgs e)
        {
            GotoHeader();
        }

        /// <summary>
        /// 在工具栏左侧图像预览区域预览当前 Markdown 链接文本指向的图像文件。
        /// </summary>
        private void MciPreviewImage_Click(object sender, RoutedEventArgs e)
        {
            PreviewImage();
        }

        /// <summary>
        /// 在工具栏左侧图像预览区域预览当前 Markdown 链接文本指向的图像文件。
        /// </summary>
        public void PreviewImage()
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            if (edit.GotoAnchorInSameDocument()) return;

            var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
            var lineText = edit.EditorBase.Document.GetText(line.Offset, line.EndOffset - line.Offset);
            var startIndex = edit.EditorBase.SelectionStart - line.Offset;

            var leftIndex = lineText.IndexOf("](") + 1;
            var rightIndex = lineText.IndexOf(')', startIndex);
            if (edit.PreviewQuickLinkedImage(false)) return;
            if (leftIndex <= 0 || rightIndex <= 1 || rightIndex <= leftIndex + 1) return;

            var subText = lineText.Substring(leftIndex + 1, rightIndex - leftIndex - 1);

            if (subText.ToLower().StartsWith("file:///"))
            {
                //文件绝对链接。
                LMessageBox.Show("不是有效图像文件链接，无法预览！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (subText.ToLower().Contains(".html"))
            {
                //文件绝对链接。
                LMessageBox.Show("不是有效图像文件链接，无法预览！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            else
            {
                try
                {
                    edit.PreviewLinkedImageFile(false);
                }
                catch (Exception ex)
                {
                    var lower = subText.ToLower();

                    if (lower.EndsWith(".jpg") || lower.EndsWith(".jpeg") || lower.EndsWith(".gif") ||
                        lower.EndsWith(".png") || lower.EndsWith("bmp"))
                    {
                        edit.PreviewLinkedImageFile(false);
                    }
                    else
                    {
                        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                    }
                }
            }
        }

        /// <summary>
        /// 跳转到当前活动编辑器中当前插入点位置处的链接（图像链接/文档链接/网址）指向的对象。
        /// </summary>
        private void MciGotoLink_Click(object sender, RoutedEventArgs e)
        {
            GotoLink();
        }

        /// <summary>
        /// 查找所有标题，以便快速向某个标题跳转。
        /// </summary>
        private void GotoHeader()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在标题间跳转",
            };

            FindHeaders(@"^ {0,3}#{1,6}.*", gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 查找所有任务列表，以便快速向某个任务列表跳转。
        /// </summary>
        private void GotoTaskListItem()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在任务列表间跳转",
            };

            FindTaskList("", gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 查找所有 TODO Comment，以便跳转到某个 TODO Comment。
        /// </summary>
        /// <param name="mark">传入“TODO”（或“DOING”、“DONE”）。</param>
        private void GotoTodoComment(string mark)
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在 TODO 标记间跳转",
            };

            FindTodoComment((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), gotoWindow.treeView, mark);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 跳转到当前活动编辑器中当前插入点位置处的链接（图像链接/文档链接/网址）指向的对象。
        /// </summary>
        private void GotoLink()
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            if (edit.GotoAnchorInSameDocument()) return;

            var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
            var lineText = edit.EditorBase.Document.GetText(line.Offset, line.EndOffset - line.Offset);
            var startIndex = edit.EditorBase.SelectionStart - line.Offset;

            var leftIndex = lineText.IndexOf("](") + 1;
            var rightIndex = lineText.IndexOf(')', startIndex);
            if (leftIndex <= 0 || rightIndex <= 1 || rightIndex <= leftIndex + 1) return;

            var subText = lineText.Substring(leftIndex + 1, rightIndex - leftIndex - 1);

            Regex regTail = new Regex(" {1,}[\"].*[\"]$");
            var matchTail = regTail.Match(subText);
            if (matchTail.Success)
            {
                subText = subText.Substring(0, matchTail.Index);
            }

            if (subText.ToLower().StartsWith("file:///"))
            {
                //文件绝对链接。
                var absoluteFullPath = subText.Substring(8);
                if (File.Exists(absoluteFullPath) || Directory.Exists(absoluteFullPath))
                {
                    System.Diagnostics.Process.Start("explorer.exe", $"\"{absoluteFullPath}\"");
                }
                else
                {
                    LMessageBox.Show("不是有效链接，无法打开！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                }
                return;
            }

            var lowerSubText = subText.ToLower();
            if (lowerSubText.Contains(".html") && lowerSubText.StartsWith("http://") == false &&
                lowerSubText.StartsWith("https://") == false && lowerSubText.StartsWith("ftp://") == false)
            {
                edit.OpenLinkedMarkdownFile();
            }
            else
            {
                try
                {
                    Regex urlReg = new Regex(@"([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?");
                    var matchUrl = urlReg.Match(subText);

                    if (matchUrl.Success)
                    {
                        var tmp = subText.ToLower();
                        if (tmp.StartsWith("http://") == false &&
                            tmp.StartsWith("https://") == false &&
                            tmp.StartsWith("ftp://") == false)
                        {
                            tmp = "http://" + subText;
                        }

                        previewFrame.Source = new Uri(tmp);
                        tcRightToolBar.SelectedItem = tiHtmlPreview;//显示预览页
                        return;
                    }
                    else edit.PreviewLinkedImageFile();
                }
                catch (Exception ex)
                {
                    var lower = subText.ToLower();

                    if (lower.EndsWith(".jpg") || lower.EndsWith(".jpeg") || lower.EndsWith(".gif") ||
                        lower.EndsWith(".png") || lower.EndsWith("bmp"))
                    {
                        var imageTitle = edit.PreviewLinkedImageFile(true);
                        if (string.IsNullOrEmpty(imageTitle))
                        {
                            LMessageBox.Show("貌似不是个正确的图像链接！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                    }
                    else
                    {
                        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                    }
                }
            }
        }

        /// <summary>
        /// 切换当前活动编辑器中当前行的标题层级。
        /// </summary>
        /// <param name="newLevel">新标题层级</param>
        private void SwitchTitleLevel(int newLevel)
        {
            var activeEditor = this.ActivedEditor;
            if (activeEditor == null) return;

            activeEditor.EditorBase.SwitchTitleLevel(newLevel);
        }

        /// <summary>
        /// 用快捷键切换当前工作区。通常是使用 Ctrl+Shift+Alt+数字键。
        /// </summary>
        /// <param name="isCtrl">Ctrl键状态。</param>
        /// <param name="isShift">Shift键状态。</param>
        /// <param name="isAlt">Alt键状态。</param>
        /// <param name="num">历史工作区列表中工作区条目的序号（1-9).</param>
        private void ChangeWorkspaceByShortCut(bool isCtrl, bool isShift, bool isAlt, int num)
        {
            if (isAlt && isCtrl && !isShift)
            {
                var index = num - 1;
                if (index < 0 || index >= lbxHistoryWorkspaces.Items.Count)
                {
                    LMessageBox.Show("历史工作区中没有对应条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }

                lbxHistoryWorkspaces.SelectedIndex = index;

                if (tcManagerPanels.SelectedItem != tiRecentlyWorkspaces)
                {
                    tcManagerPanels.SelectedItem = tiRecentlyWorkspaces;
                }

                if (cdLeftToolsArea.ActualWidth <= 0)
                {
                    cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
                }

                var item = lbxHistoryWorkspaces.Items[num - 1] as RecentDirectoryListBoxItem;
                if (item != null && item.DirectoryPath != Globals.PathOfWorkspace)
                {
                    var result = LMessageBox.Show("真的要把工作区切换到以下目录吗？\r\n\r\n　　" + item.DirectoryPath, Globals.AppName,
                         MessageBoxButton.YesNo, MessageBoxImage.Question);
                    if (result == MessageBoxResult.Yes)
                    {
                        ChangeWorkspace(item.DirectoryPath);
                    }
                }
            }
        }

        /// <summary>
        /// 处理与主界面其它区域可能冲突的快捷键。
        /// 这样在主界面其它区仍然可以使用这些快捷键。
        /// 例如，可以在“历史工作区目录”继续使用 Ctrl+Shift+8，作为快捷键。
        /// </summary>
        void mainTabControl_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
            KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
            KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
            KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
            KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
            KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

            bool isCtrl, isShift, isAlt;

            isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
            isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
            isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;
            switch (e.Key)
            {
                case Key.D8:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (this.mainTabControl.SelectedItem != null)
                            {
                                var mdi = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (mdi != null)
                                {
                                    mdi.EditorBase.SwitchListMark(!isShift);
                                    e.Handled = true;
                                }
                            }
                        }

                        break;
                    }
            }
        }

        /// <summary>
        /// 双击状态栏上的当前工作区路径标签，在 Windows Explorer 中打开该目录。
        /// </summary>
        private void tbxPathOfWorkspace_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                OpenWorkspaceWithExplorer();
            }
        }

        private void OpenWorkspaceWithExplorer()
        {
            if (Directory.Exists(Globals.PathOfWorkspace) == false)
            {
                LMessageBox.Show(string.Format("　　指定的工作区目录[{0}]不存在。这可能是因为程序被安装到Window系统盘的某个目录中，又未获得管理员权限，导致无法创建工作区目录。",
                    Globals.PathOfWorkspace), Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            System.Diagnostics.Process.Start("explorer.exe", $"\"{Globals.PathOfWorkspace}\"");
        }

        /// <summary>
        /// 在工作区选定位置创建新 Markdown 文件。
        /// </summary>
        private void btnNew_Click_1(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 在工作区选定位置创建新 Markdown 文件。
        /// </summary>
        private void miNewdocument_Click_1(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 创建工作区选定目录条目对应的元 Markdonw 文件。
        /// 因为早已改成创建目录时自动添加对应的目录元文件，所以此方法已不必存在。
        /// 保留代码仅备查。
        /// </summary>
        //private void miNewFolderMetaDocument_Click(object sender, RoutedEventArgs e)
        //{
        //    NewFile(false, true);
        //}

        /// <summary>
        /// 保存当前正在编辑的 Markdown 文档（活动编辑器中的文档）。
        /// </summary>
        private void btnSave_Click_1(object sender, RoutedEventArgs e)
        {
            SaveDocment(miFormatBeforeSave.IsChecked);
        }

        /// <summary>
        /// 保存当前正在编辑的 Markdown 文档（活动编辑器中的文档）。
        /// </summary>
        private void miSave_Click_1(object sender, RoutedEventArgs e)
        {
            SaveDocment(miFormatBeforeSave.IsChecked);
        }

        /// <summary>
        /// 打开指定路径的 Markdown 文件（一个或多个）。
        /// </summary>
        /// <param name="fullPathOfFiles">要打开的 Markdown 的文件的路径列表。</param>
        public void OpenDocuments(string[] fullPathOfFiles)
        {
            List<string> files = new List<string>();
            foreach (string s in fullPathOfFiles)
            {
                files.Add(s);
            }
            OpenDocuments(files);
        }

        /// <summary>
        /// 根据指定的路径列表打开这些 Markdown 文件。
        /// </summary>
        /// <param name="fullPathOfFiles">包含 Markdown 文件磁盘路径的列表。</param>
        public void OpenDocuments(List<string> fullPathOfFiles)
        {
            if (fullPathOfFiles == null || fullPathOfFiles.Count <= 0) return;

            StringBuilder errorMsg = new StringBuilder();

            foreach (string s in fullPathOfFiles)
            {
                MarkdownEditor openedFileItem = null;

                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti != null && eti.FullFilePath == s)
                    {
                        openedFileItem = eti;
                        break;
                    }
                }

                if (openedFileItem != null)
                {
                    this.mainTabControl.SelectedItem = openedFileItem;
                    continue;//已打开的文档，不再重复打开，只是使其成为活动文档。
                }

                MarkdownEditor newEditor = new MarkdownEditor("md_" + (mainTabControl.Items.Count + 1),
                    this.IsExamEnabled, this.IsAutoCompletionEnabled, this.IsEnToChineseDictEnabled,
                    this.IsShowSpaces, this.IsShowEndOfLine, this.IsShowTabs, this.TextAutoWrap, false, this.HighlightingSetting);

                string openResult = newEditor.OpenDocument(s);
                if (openResult == string.Empty)
                {
                    this.mainTabControl.Items.Insert(this.mainTabControl.SelectedIndex + 1, newEditor);
                    this.mainTabControl.SelectedIndex += 1;
                }
                else
                {
                    errorMsg.Append(openResult);
                }

                newEditor.Saved += NewDocument_Saved;
            }

            //如果打开了“演讲者模式”，就在打开文档后刷新大纲视图
            if (Globals.MainWindow.PerspectiveMode == Perspective.EditingAndPresentation)
            {
                var activeEditor = ActivedEditor;
                if (activeEditor != null)
                {
                    activeEditor.EditorBase.UpdateLayout();
                    cmbSearchArea2.SelectedIndex = 0;
                    btnRefreshOutLine_Click(null, null);
                }
            }

            string em = errorMsg.ToString();
            if (em != string.Empty)
            {
                LMessageBox.Show("打开文件时出现异常。消息如下：\r\n" + em,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }

            RefreshTextAutoWrapToStatusBar();
        }

        /// <summary>
        /// 创建一个新文件后，在工作区管理器中添加对应的条目。
        /// </summary>
        private void NewDocument_Saved(object sender, DocumentSavedEventArgs e)
        {
            var item = FindWorkspaceTreeViewItem(e.FileFullName);
            if (item == null) return;

            item.RefreshFileState();
        }

        /// <summary>
        /// 保存当前打开的所有 Markdown 文档。
        /// </summary>
        private void miSaveAll_Click_1(object sender, RoutedEventArgs e)
        {
            SaveAllDocumentsAndOptions();
        }

        /// <summary>
        /// 退出应用程序。
        /// </summary>
        private void miExit_Click_1(object sender, RoutedEventArgs e)
        {
            //App.Current.Shutdown();//注意，这能使用这个
            this.isForceExit = true;//强行忽略这个字段的值。
            this.Close();
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        private void miUndo_Click_1(object sender, RoutedEventArgs e)
        {
            Undo();
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        private void miRedo_Click_1(object sender, RoutedEventArgs e)
        {
            Redo();
        }

        /// <summary>
        /// 从当前活动编辑器剪切选定的文本。
        /// </summary>
        private void miCut_Click_1(object sender, RoutedEventArgs e)
        {
            Cut();
        }

        /// <summary>
        /// 从当前活动编辑器复制选定的文本。
        /// </summary>
        private void miCopy_Click_1(object sender, RoutedEventArgs e)
        {
            Copy();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴数据。
        /// </summary>
        private void miPaste_Click_1(object sender, RoutedEventArgs e)
        {
            Paste();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码（会对粘贴的代码自动添加行号）。
        /// </summary>
        private void miPasteCode_Click(object sender, RoutedEventArgs e)
        {
            PasteCode();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码（会对粘贴的代码自动添加行号）。
        /// </summary>
        public void PasteCode()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.PasteCode();
        }

        /// <summary>
        /// 让当前活动编辑器执行“全选”操作。
        /// </summary>
        private void miSelectAll_Click_1(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.SelectAll();
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        private void btnUndo_Click_1(object sender, RoutedEventArgs e)
        {
            Undo();
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        private void btnRedo_Click_1(object sender, RoutedEventArgs e)
        {
            Redo();
        }

        /// <summary>
        /// 从当前活动编辑器中剪切选定的文本。
        /// </summary>
        public void Cut()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Cut();
        }

        /// <summary>
        /// 从当前活动编辑器中剪切选定的文本。
        /// </summary>
        private void btnCut_Click_1(object sender, RoutedEventArgs e)
        {
            Cut();
        }

        /// <summary>
        /// 从当前活动编辑器中复制选定的文本。
        /// </summary>
        public void Copy()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Copy();
        }

        /// <summary>
        /// 从当前活动编辑器中复制选定的文本。
        /// </summary>
        private void btnCopy_Click_1(object sender, RoutedEventArgs e)
        {
            Copy();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴文本。
        /// </summary>
        public void Paste()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.Paste();
        }

        /// <summary>
        /// 向当前活动编辑器执行粘贴操作。（可能是文本，也可能是图像链接。）
        /// </summary>
        private void btnPaste_Click_1(object sender, RoutedEventArgs e)
        {
            Paste();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码。
        /// </summary>
        private void btnPasteCode_Click(object sender, RoutedEventArgs e)
        {
            PasteCode();
        }

        /// <summary>
        /// 刷新各编辑器的字号。
        /// </summary>
        private void RefreshFontSize()
        {
            //foreach (var item in this.mainTabControl.Items)
            //{
            //    MarkdownEditor eti = item as MarkdownEditor;
            //    if (eti == null) continue;

            //    eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            //    //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //    //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //    //TODO： 暂时去除它。
            //}

            //foreach (var item in this.tcRightToolBar.Items)
            //{
            //    MarkdownEditor eti = item as MarkdownEditor;
            //    if (eti == null) continue;

            //    eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            //    //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //    //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //    //TODO： 暂时去除它。
            //}

            //foreach (var item in this.tcManagerPanels.Items)
            //{
            //    MarkdownEditor eti = item as MarkdownEditor;
            //    if (eti == null) continue;

            //    eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            //    //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //    //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //    //TODO： 暂时去除它。
            //}

            //大纲视图也支持字号变化
            tvOutLine.FontSize = this.mainTabControl.FontSize;
        }

        /// <summary>
        /// 放大编辑器的字号。
        /// </summary>
        internal void FontSizeUp()
        {
            if (this.mainTabControl.FontSize <= 98)
            {
                this.mainTabControl.FontSize += 2;//最大字号100。

                //右工具栏中的对照区不能直接对右工具栏变化字号
                foreach (var item in this.tcRightToolBar.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                //左工具栏中的对照区不能直接对左工具栏变化字号
                foreach (var item in this.tcManagerPanels.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                App.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                RefreshFontSize();
            }
        }

        private void btnFontSizeUp_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeUp();
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        internal void FontSizeDown()
        {
            if (this.mainTabControl.FontSize >= 8)
            {
                this.mainTabControl.FontSize -= 2;//最小字号6。

                //右工具栏中的对照区不能直接对右工具栏变化字号
                foreach (var item in this.tcRightToolBar.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                //左工具栏中的对照区不能直接对左工具栏变化字号
                foreach (var item in this.tcManagerPanels.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                App.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                RefreshFontSize();
            }
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        private void btnFontSizeDown_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeDown();
        }

        /// <summary>
        /// 放大编辑器的字号。
        /// </summary>
        private void miFontSizeUp_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeUp();
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        private void miFontSizeDown_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeDown();
        }

        // <summary>
        /// 重置当前所有编辑器的默认字号。
        /// </summary>
        private void miFontSizeReset_Click_1(object sender, RoutedEventArgs e)
        {
            this.mainTabControl.FontSize = 16;//默认字号。
            RefreshFontSize();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上加粗标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithBold()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            eti.EditorBase.SwitchBold();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上倾斜标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithItalic()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            eti.EditorBase.SwitchItalic();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上下划线标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithUTag()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (eti.EditorBase.SelectedText.Length <= 0)
            {
                eti.EditorBase.SelectedText = "<u>" + eti.EditorBase.SelectedText + "</u>";
                eti.EditorBase.Select(eti.EditorBase.SelectionStart + 3, 0);
            }
            else
            {
                eti.EditorBase.SelectedText = "<u>" + eti.EditorBase.SelectedText + "</u>";
            }
        }

        /// <summary>
        /// 给当前活动编辑器中的选定文本两侧加上删除线标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithSTag()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            if (eti.EditorBase.SelectedText.Length <= 0)
            {
                eti.EditorBase.SelectedText = "[=" + eti.EditorBase.SelectedText + "=]";
                eti.EditorBase.Select(eti.EditorBase.SelectionStart + 2, 0);
            }
            else
            {
                eti.EditorBase.SelectedText = "[=" + eti.EditorBase.SelectedText + "=]";
            }
        }

        /// <summary>
        /// 选取当前活动编辑器插入点位置的整行文本。（不能跨行，如果当前选择区是跨行的，只会选中选择区起始位置所在的行。）
        /// </summary>
        private void SelecetLineOrMoveToVerticalCenter(bool isShift)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            try
            {
                if (isShift)
                    eti.EditorBase.SelectLine();
                else
                {
                    double vertOffset = eti.EditorBase.TextArea.TextView.GetVisualTopByDocumentLine(eti.EditorBase.Document.GetLineByOffset(eti.EditorBase.SelectionStart).LineNumber);
                    eti.EditorBase.ScrollToVerticalOffset(vertOffset - eti.EditorBase.TextArea.TextView.ActualHeight / 2);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 选取当前活动编辑器插入点位置的整行文本。（不能跨行，如果当前选择区是跨行的，只会选中选择区起始位置所在的行。）
        /// </summary>
        private void miSelectLine_Click_1(object sender, RoutedEventArgs e)
        {
            SelecetLineOrMoveToVerticalCenter(true);
        }

        /// <summary>
        /// 弹出“关于”框，显示本程序相关信息。
        /// </summary>
        private void miAbout_Click_1(object sender, RoutedEventArgs e)
        {
            AboutBox abox = new AboutBox(this) { Owner = App.Current.MainWindow, };
            abox.ShowDialog();
        }

        /// <summary>
        /// 保存当前打开的所有 Markdown 文档。
        /// </summary>
        private void btnSaveAll_Click(object sender, RoutedEventArgs e)
        {
            SaveAllDocumentsAndOptions();
        }

        /// <summary>
        /// 编译、预览当前活动编辑器生成的 Html 页面。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void previewHtml_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 以全屏方式预览由当前活动编辑器编译生成的 Html 网页。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void previewFullScreenHtml_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 编译当前活动编辑器为 Html 网页文件，并在浏览器中预览。
        /// </summary>
        /// <param name="callSystemDefaultExplorer">是否调用 Windows 默认网页浏览器来预览。默认为 false，表示直接使用 LME 自带的浏览器预览。</param>
        public void CompileAndPreviewHtml(bool callSystemDefaultExplorer = false)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified)
            {
                var messageResult = LMessageBox.Show("文档需要先保存，要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageResult != MessageBoxResult.Yes) return;

                if (Globals.MainWindow.miFormatBeforeSave.IsChecked)
                {
                    eti.FormatedMarkdownText();
                }

                string result;

                if (File.Exists(eti.FullFilePath))
                {
                    result = eti.SaveDocument();
                }
                else
                {
                    result = SaveNewFile(ActivedEditor);
                }

                AnchorsManager.Load();

                if (result != string.Empty) return;
            }

            eti.CompileAndPreviewHtml();
        }

        /// <summary>
        /// 将当前编辑器内的文本按水平线分割成小文并分别编译，然后再演示。
        /// </summary>
        public void CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType presentateHtmlSplitterType, bool onlyPresentateHeaders = false)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified)
            {
                var messageResult = LMessageBox.Show("文档需要先保存，要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageResult != MessageBoxResult.Yes) return;

                if (Globals.MainWindow.miFormatBeforeSave.IsChecked)
                {
                    eti.FormatedMarkdownText();
                }

                string result;

                if (File.Exists(eti.FullFilePath))
                {
                    result = eti.SaveDocument();
                }
                else
                {
                    result = SaveNewFile(ActivedEditor);
                }

                AnchorsManager.Load();

                if (result != string.Empty) return;
            }

            eti.CompileAndPresentateHtml(presentateHtmlSplitterType, onlyPresentateHeaders);
        }

        /// <summary>
        /// 在工作区管理器当前选定项所指向的目录下，创建新 Markdown 文档。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miNewFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 在工作区管理器当前选定项所指向的目录下，创建新示例文档。
        /// </summary>
        private void miNewSampleFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(true);
        }

        /// <summary>
        /// 在工作区管理器当前选定项的位置新建一个 Markdown 文档。
        /// 如果当前项代表目录，则在该目录下面创建新 Markdown 文档；
        /// 如果当前项代表某个 Markdown 文件，则在该 Markdown 文件所在的文件夹下创建新 Markdown 文件。
        /// </summary>
        /// <param name="isSampleFile">是否示例文档。如为 true，则创建的文档带内容。</param>
        /// <param name="isMetaFile">是否目录元文件。</param>
        /// <param name="parentItem">最后两个参数是供“选中一个目录时添加同级Markdown文件”这种特殊情况提供的。</param>
        /// <param name="indexInParentItems">最后两个参数是供“选中一个目录时添加同级Markdown文件”这种特殊情况提供的。</param>
        private void NewFile(bool isSampleFile, bool isMetaFile = false, WorkspaceTreeViewItem parentItem = null, int? indexInParentItems = null)
        {
            WorkspaceTreeViewItem wtvi;
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先在主界面左侧工作区管理器窗口中选定一个目录来创建文件。" +
                    "如果看不到工作区浏览窗口，请按F12键显示左工具栏。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }
            else
            {
                wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            }

            string directoryPath;
            WorkspaceTreeViewItem destWtvi;

            if (wtvi.IsMarkdownFilePath)
            {
                directoryPath = wtvi.ParentDirectory;
                destWtvi = wtvi.ParentWorkspaceTreeViewItem;
            }
            else
            {
                directoryPath = wtvi.FullPath;
                destWtvi = wtvi;
            }

            if (Directory.Exists(directoryPath) == false)
            {
                var lastIndex = directoryPath.LastIndexOf("\\");
                if (lastIndex < 0)
                {
                    LMessageBox.Show("只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var parentDirectory = directoryPath.Substring(0, lastIndex);
                if (Directory.Exists(parentDirectory) == false)
                {
                    LMessageBox.Show("只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                directoryPath = parentDirectory;
            }

            if (directoryPath.EndsWith("~") || directoryPath.EndsWith("~\\"))
            {
                LMessageBox.Show("以波形符结尾的目录是程序自动添加的资源目录，不能在其中再建立MarkDown文件。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (directoryPath.EndsWith("\\") == false) directoryPath += "\\";

            string newShortFileName;

            if (isMetaFile)
            {
                newShortFileName = "_" + new DirectoryInfo(directoryPath).Name + ".md";
            }
            else
            {
                newShortFileName = InputBox.Show(Globals.AppName, "请输入新文件名（不能以“_”开头）：", "", true,
                   "说明：\r\n　　⑴不需要输入后缀名；\r\n　　⑵请尽可能设定有意义的文件名。\r\n　　⑶不能以下划线开头。\r\n　　⑷尽量以字母开头。\r\n　　因为此文件名很可能将来被其它文件引用。所以，如非必要，创建之后请勿随意更改文件名。");
            }

            if (string.IsNullOrWhiteSpace(newShortFileName)) return;  // 用户放弃新建文件。

            var newShortCommentName = newShortFileName;
            newShortFileName = ChinesePinYin.ToChinesePinYinText(newShortCommentName);

            if (AskEnglishFileName)
            {
                var regValidFileNameChars = new Regex(@"^[a-zA-Z0-9_][a-zA-Z0-9\-~_\.]{0,}");
                if (regValidFileNameChars.IsMatch(newShortCommentName) == false)
                {
                    // 为什么要用英文文件名？
                    // 这是因为 CHM 对中文路径的支持有问题——很多网络设备对中文路径的支持也有问题。
                    newShortFileName = InputBox.Show(Globals.AppName, $"您输入的【{newShortCommentName}】将被用作文档标题。\r\n\r\n由于含有中文或特殊字符，建议使用下列字符串作文件名：",
                        newShortFileName, true, "说明：\r\n　　⑴建议的文件名是取中文拼音首字母生成的。\r\n　　⑵您可以在这里改成有意义的英文字符串。\r\n　　⑶空白字符会被替换为连字符。", false, true);
                    if (string.IsNullOrWhiteSpace(newShortFileName)) return;  // 用户放弃新建文件。
                }
            }

            newShortFileName = ChinesePinYin.ToChinesePinYinText(newShortFileName.Replace(" ", "-").Replace("　", "-").Replace("\t", "-"));  // 防止用户仍然不慎输入了中文。

            if (newShortFileName.ToLower().EndsWith(".md") == false) newShortFileName += ".md";

            if (string.IsNullOrEmpty(newShortFileName))
            {
                LMessageBox.Show("文件名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var newFilePath = directoryPath + newShortFileName;
            if (File.Exists(newFilePath))
            {
                LMessageBox.Show("已存在同名文件，无法完成操作！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            try
            {
                string docTitle = newShortFileName;
                using (StreamWriter sw = File.CreateText(newFilePath))
                {
                    if (isSampleFile)
                    {
                        sw.Write(Properties.Resources.example);
                    }
                    else
                    {
                        docTitle = newShortCommentName.EndsWith(".md") ? newShortCommentName.Substring(0, newShortCommentName.Length - 3) : newShortCommentName;

                        Regex headerNumberRegex = new Regex(@"^[lL]\d{7,}[ 　-]");
                        var matchHeaderNumber = headerNumberRegex.Match(docTitle);
                        if (matchHeaderNumber.Success)
                        {
                            docTitle = docTitle.Substring(matchHeaderNumber.Length);
                        }

                        var commonTemplateText = "";
                        if (File.Exists(Globals.PathOfWorkspaceCommonTemplateFile))
                        {
                            using (StreamReader sr = File.OpenText(Globals.PathOfWorkspaceCommonTemplateFile))
                            {
                                commonTemplateText = sr.ReadToEnd();
                            }
                        }

                        bool isWorkspaceMetaFile = false;
                        if (isMetaFile)
                        {
                            if (directoryPath.ToLower() == Globals.PathOfWorkspace.ToLower()) isWorkspaceMetaFile = true;
                        }

                        var metaInlineEnviromentSpan = isMetaFile ? (isWorkspaceMetaFile ? Properties.Resources.workspaceMetaFileInlineEnviromentTextSpan : Properties.Resources.metaFileInlineEnviromentTextSpan) : "";

                        sw.Write($"\r\n%{docTitle}\r\n\r\n；{DateTime.Now.ToString()}\r\n{metaInlineEnviromentSpan}\r\n{commonTemplateText}");
                    }
                }

                WorkspaceTreeViewItem newtvi = new WorkspaceTreeViewItem(newFilePath, Globals.MainWindow);
                if (isMetaFile == false)
                {
                    //2017年7月21日，现在不需要再排序了，让用户决定添加在哪里
                    //如果是添加下级，直接添加到下级子节点的最后一个位置。
                    //如果是添加同级，直接添加到当前节点的后面。
                    #region 废弃代码
                    //List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                    //int baseIndex = 0;
                    //foreach (var item in destWtvi.Items)
                    //{
                    //    var wi = item as WorkspaceTreeViewItem;
                    //    if (wi != null)
                    //    {
                    //        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                    //        {
                    //            baseIndex++;
                    //            continue;
                    //        }
                    //        tmpItems.Add(wi);
                    //    }
                    //}
                    //tmpItems.Add(newtvi);
                    //tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                    //var index = tmpItems.IndexOf(newtvi) + baseIndex;
                    //destWtvi.Items.Insert(index, newtvi);
                    #endregion

                    //这种情况下，如果选中的是目录，就添加到目录节点的子节点中最后一个位置；
                    //如果选中的是 Markdown 文件，就添加到此文件节点的后一个位置。
                    if (parentItem != null && indexInParentItems.HasValue)
                    {
                        //这是供“选中一个目录时添加同级Markdown文件”这种特殊情况提供的。
                        parentItem.Items.Insert(indexInParentItems.Value, newtvi);
                    }
                    else
                    {
                        if (wtvi.IsMarkdownFilePath)
                        {
                            var index = destWtvi.Items.IndexOf(wtvi);
                            destWtvi.Items.Insert(index + 1, newtvi);
                        }
                        else
                        {
                            //选中目录
                            destWtvi.Items.Add(newtvi);
                        }
                    }

                    newtvi.IsSelected = true;
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }//元文件不显示。

                OpenDocuments(new string[] { newFilePath });

                //TODO: 仍然无法实现使编辑器获取焦点
                FocusActiveEditor();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("新建文件失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// 将字符串开头的数字去除。例如：1－2－3－xxx，格式化后只保留xxx。但如果只剩下空格，就返回原始值。
        /// </summary>
        /// <param name="src">源文本。</param>
        /// <returns>返回去除了数字前缀的文件短名。</returns>
        public static string FormatDocumentTitle(string src)
        {
            if (string.IsNullOrWhiteSpace(src)) return string.Empty;

            if (src.ToLower().EndsWith(".html"))
            {
                src = src.Substring(0, src.Length - 5);
            }

            if (src.ToLower().EndsWith(".md"))
            {
                src = src.Substring(0, src.Length - 3);
            }

            Regex regex = new Regex(@"^[ 　\t\-_]*"); //new Regex(@"^[ 　\t0123456789１２３４５６７８９０\-_]*");
            var match = regex.Match(src);
            string result = null;
            if (match.Success)
                result = src.Substring(match.Length);

            if (string.IsNullOrWhiteSpace(result))
            {
                return src;
            }

            return result;
        }

        /// <summary>
        /// 删除工作区管理器中某个文件或目录。
        /// </summary>
        private void miDeleteFile_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中要删除的文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi.FullPath == Globals.PathOfWorkspace)
            {
                LMessageBox.Show("工作区目录不能直接删除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show($"文件【{wtvi.Title}({wtvi.ShortName})】或目录是程序自动生成的，不能直接删除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                if (Directory.Exists(wtvi.FullPath))
                {
                    var result = LMessageBox.Show("删除目录会删除其中的所有文件和子目录，而且不会保留这些文件当前正在编辑的内容！\r\n\r\n" +
                        $"　　真的要删除【{wtvi.Title}({wtvi.ShortName})】目录吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                    if (result != MessageBoxResult.Yes) return;

                    Directory.Delete(wtvi.FullPath, true);
                    //workspaceManager.Refresh(null);
                    var parent = wtvi.Parent as WorkspaceTreeViewItem;
                    if (parent != null && parent.Items.Contains(wtvi))
                    {
                        var currentItemIndex = parent.Items.IndexOf(wtvi);
                        parent.Items.Remove(wtvi);
                        if (currentItemIndex >= 0)
                        {
                            if (currentItemIndex < parent.Items.Count)
                            {
                                if (parent.Items[currentItemIndex] is WorkspaceTreeViewItem newCurrentItem)
                                {
                                    newCurrentItem.IsSelected = true;
                                }
                                else parent.IsSelected = true;
                            }
                            else if (currentItemIndex >= 1 && currentItemIndex == parent.Items.Count)
                            {
                                if (parent.Items[currentItemIndex - 1] is WorkspaceTreeViewItem newCurrentItem)
                                {
                                    newCurrentItem.IsSelected = true;
                                }
                                else parent.IsSelected = true;
                            }
                            else parent.IsSelected = true;
                        }
                    }

                    // 根本不必递归，直接比较文件路径前端就可以了
                    for (int i = mainTabControl.Items.Count - 1; i >= 0; i--)
                    {
                        var edit = mainTabControl.Items[i] as MarkdownEditor;
                        if (edit.FullFilePath.ToLower().StartsWith(wtvi.FullPath.ToLower()))
                        {
                            mainTabControl.Items.RemoveAt(i);
                        }
                    }

                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                    return;
                }

                if (File.Exists(wtvi.FullPath))
                {
                    if (wtvi.IsImageFileExist)
                    {
                        var result = LMessageBox.Show($"删除文件会导致对此文件的链接失效。所以，在确定没有链接到此文件的链接时才应删除文件。\r\n\r\n　　真的要删除下列图像文件吗？\r\n　　：【{wtvi.Title}({wtvi.ShortName})】", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                        if (result == MessageBoxResult.Yes)
                        {
                            ImagePreview.Source = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/App.png"));
                            tbImageTitle.Text = "";
                            tbImageTitle.Visibility = Visibility.Collapsed;

                            File.Delete(wtvi.FullPath);

                            var parentItem = (wtvi.Parent as TreeViewItem);
                            parentItem.Items.Remove(wtvi);
                            parentItem.IsSelected = true;
                        }
                    }
                    else if (wtvi.IsMarkdownFilePath)
                    {
                        try
                        {
                            var result = LMessageBox.Show($"删除文件会导致对此文件的链接失效。所以，在确定没有链接到此文件的链接时才应删除文件。\r\n\r\n　　真的要删除下列文件吗？\r\n　　：【{wtvi.Title}({wtvi.ShortName})】", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                            if (result == MessageBoxResult.Yes)
                            {
                                DirectoryInfo resourceDirectoryInfo = new DirectoryInfo(wtvi.ResourceDirectoryFullPath);

                                MarkdownEditor openedEditor = null;
                                foreach (var item in this.mainTabControl.Items)
                                {
                                    var editor = item as MarkdownEditor;
                                    if (editor == null) continue;

                                    if (editor.FullFilePath.ToLower() == wtvi.FullPath.ToLower())
                                    {
                                        openedEditor = editor;
                                        break;
                                    }
                                }

                                if (openedEditor != null)
                                {
                                    this.mainTabControl.Items.Remove(openedEditor);
                                }

                                File.Delete(wtvi.FullPath);

                                if (Directory.Exists(resourceDirectoryInfo.FullName))
                                {
                                    Directory.Delete(resourceDirectoryInfo.FullName, true);
                                }

                                var parentItem = (wtvi.Parent as TreeViewItem);
                                var currentItemIndex = parentItem.Items.IndexOf(wtvi);
                                if (currentItemIndex >= 0)
                                {
                                    parentItem.Items.Remove(wtvi);
                                    if (currentItemIndex < parentItem.Items.Count)
                                    {
                                        if (parentItem.Items[currentItemIndex] is WorkspaceTreeViewItem nextItem)
                                        {
                                            nextItem.IsSelected = true;
                                        }
                                    }
                                    else if (currentItemIndex >= 1 && currentItemIndex == parentItem.Items.Count)
                                    {
                                        if (parentItem.Items[currentItemIndex - 1] is WorkspaceTreeViewItem newCurrentItem)
                                        {
                                            newCurrentItem.IsSelected = true;
                                        }
                                        else parentItem.IsSelected = true;
                                    }
                                    else parentItem.IsSelected = true;

                                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void MiRemoveEntry_Click(object sender, RoutedEventArgs e)
        {

            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中要移除的条目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi.FullPath == Globals.PathOfWorkspace)
            {
                LMessageBox.Show("工作区根不能直接移除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show($"此条目【{wtvi.Title}({wtvi.ShortName})】是程序自动生成的，不能直接移除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var parentItem = wtvi.ParentWorkspaceTreeViewItem;
            if (parentItem == null)
            {
                LMessageBox.Show("此条目没有上级，不能移除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (parentItem.Items.Contains(wtvi) == false)
            {
                LMessageBox.Show("发生意外，找到的父级可能不是真的父级。不能移除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            parentItem.Items.Remove(wtvi);
            WorkspaceManager.SaveWorkspaceTreeviewToXml();
        }

        /// <summary>
        /// 将当前 Markdown 文件、Image 文件、目录元文件的链接插入到当前活动编辑器的插入点位置处。
        /// </summary>
        private void miInsertLinkToCurrentDocument_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区目录中选择一个文件作为要链接的目标文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            InsertLinkToActiveDocument(wtvi);
        }

        internal void InsertLinkToActiveDocument(WorkspaceTreeViewItem wtvi)
        {
            var linkSourceFullPath = wtvi.FullPath;

            if (wtvi.IsMarkdownFilePath == false)
            {
                if (wtvi.IsImageFileExist)
                {
                    wtvi.InsertImageTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else if (wtvi.IsSoundFileExist)
                {
                    wtvi.InsertSoundTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else if (wtvi.IsVedioFileExist)
                {
                    wtvi.InsertVedioTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else
                {
                    if (wtvi.FullPath.ToLower() == Globals.PathOfWorkspace.ToLower())
                    {
                        LMessageBox.Show("不能将工作区根目录作为链接插入到当前文档中！",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else
                    {
                        var di = new DirectoryInfo(wtvi.FullPath);
                        var directoryMdFileFullName = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md";
                        linkSourceFullPath = directoryMdFileFullName;
                        CreateDirectoryMetaMdFile(di, directoryMdFileFullName);

                        InsertLinkToActiveDocument(wtvi, linkSourceFullPath);
                        return;
                    }
                }
            }

            // 如果是普通 Markdown 文件 
            InsertLinkToActiveDocument(wtvi, linkSourceFullPath);
        }

        /// <summary>
        /// 取目录对应元文件路径。
        /// </summary>
        /// <param name="directoryFullPath">目录完整路径。</param>
        /// <returns></returns>
        public string GetMetaFilePathOfDirectory(string directoryFullPath)
        {
            if (Directory.Exists(directoryFullPath) == false) return "";
            var metaFileName = directoryFullPath;
            if (metaFileName.EndsWith("\\") == false) metaFileName += "\\";
            DirectoryInfo di = new DirectoryInfo(directoryFullPath);

            metaFileName = metaFileName + "_" + di.Name + ".md";
            return metaFileName;
        }

        /// <summary>
        /// 取目录元文件的标题。取不到会返回空字符串或null。
        /// </summary>
        /// <param name="directoryFullPath"></param>
        /// <returns></returns>
        public string GetMetaFileTitleOfDirectory(string directoryFullPath)
        {
            return GetTitleOfMdFile(GetMetaFilePathOfDirectory(directoryFullPath));
        }

        /// <summary>
        /// 在当前活动编辑器的插入点位置添加对工作区管理器中当前选定的 Markdown 文件的链接。
        /// </summary>
        /// <param name="wtvi">工作区管理器中表示某个 Markdown 文件或某个目录的条目。（如果是目录，添加的应是其对应的元文件。</param>
        /// <param name="linkSourceFullPath">源文件路径，如果是目录，应传入目录元文件的路径。</param>
        private void InsertLinkToActiveDocument(WorkspaceTreeViewItem wtvi, string linkSourceFullPath)
        {
            //将当前选定文档作为一个链接插入到正在编辑的文档中。
            var editor = Globals.MainWindow.ActivedEditor;
            if (editor == null)
            {
                LMessageBox.Show("当前没有打开任何文档，无法添加对此文件的引用！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var destFilePath = editor.FullFilePath;
            var srcPath = linkSourceFullPath;//wtvi.FullPath.ToLower();

            string shortName;

            if (File.Exists(wtvi.FullPath))
            {
                FileInfo fi = new FileInfo(wtvi.FullPath);
                shortName = fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length);
            }
            else if (Directory.Exists(wtvi.FullPath))
            {
                DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);

                //判断目录元文件是否存在
                var metaFileName = wtvi.FullPath;
                if (metaFileName.EndsWith("\\") == false) metaFileName += "\\";

                //目录元文件的短名实际上是以"_"开头的，但使用"_"会和Markdown 基本格式语法中的“倾斜”效果冲突。
                //于是自动添加的链接中就以"~"代替"_"，在编译时再转换回去。——这样视觉效果要好得多。
                metaFileName = metaFileName + "_" + di.Name + ".md";

                if (File.Exists(metaFileName))
                {
                    shortName = GetTitleOfMdFile(metaFileName);
                }
                else shortName = di.Name;
            }
            else shortName = "";

            var title = MainWindow.GetTitleOfMdFile(wtvi.FullPath);
            if (string.IsNullOrWhiteSpace(title) == false)
            {
                shortName = title;
            }

            editor.EditorBase.SelectedText = BuildLinkText(srcPath, destFilePath, ref shortName);
            var destSel = editor.EditorBase.SelectionStart + 1;
            if (destSel < editor.EditorBase.Document.TextLength)
            {
                editor.EditorBase.Select(destSel, Math.Max(FormatDocumentTitle(shortName).Length, 0));
            }
        }

        /// <summary>
        /// 按照工作区管理器中选定的 Markdown 文件条目查找所有对此文件的链接。
        /// </summary>
        private void miFindAllLinkToThisFile_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区目录中选择一个文件作为要查找的文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (wtvi.IsMarkdownFilePath == false && wtvi.IsImageFileExist == false)
            {
                LMessageBox.Show("只有Markdown文件、图片资源文件才能链接（且需要编译为Html文档才有效）！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            tvFindAndReplace.Items.Clear();

            FileInfo fi = new FileInfo(wtvi.FullPath);
            var shortName = fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length);
            var item = new FindDocumentTreeViewItem(wtvi.FullPath, wtvi.ShortName, $"{wtvi.ShortName} 被引用于：");

            FindAllLinksToThisFile(item, fi.FullName, Globals.PathOfWorkspace, shortName);

            if (item.HasItems)
            {
                tvFindAndReplace.Items.Add(item);
                item.IsExpanded = true;
            }
            else
            {
                tvFindAndReplace.Items.Add(new TreeViewItem() { Header = "<没有找到任何引用...>" });
            }

            tcRightToolBar.SelectedItem = tiFindResult;
        }

        /// <summary>
        /// 查找所有对此文件的引用。
        /// </summary>
        /// <param name="item">将查找结果放到此项目下级。</param>
        /// <param name="srcPath">查找的路径。</param>
        /// <param name="destDirectory">目的目录路径。</param>
        /// <param name="shortName">短名。</param>
        private void FindAllLinksToThisFile(FindDocumentTreeViewItem item, string srcPath, string destDirectory, string shortName)
        {
            if (File.Exists(srcPath) == false) return;

            if (Directory.Exists(destDirectory) == false) return;

            var directoryInfo = new DirectoryInfo(destDirectory);
            var files = directoryInfo.GetFiles();

            foreach (var file in files)
            {
                if (file.Extension.ToLower() != ".md" &&
                   WorkspaceTreeViewItem.IsValidateImageFilePath(file.FullName) == false) continue;

                var linkText = BuildLinkText(srcPath, file.FullName, ref shortName);
                var mIndex = linkText.IndexOf("](");
                if (linkText.StartsWith("[") && linkText.EndsWith(")") &&
                    (mIndex >= 1 && mIndex < linkText.Length - 2))
                {
                    linkText = linkText.Substring(mIndex + 1, linkText.Length - mIndex - 1);
                }
                //BuildLinkText()其实只针对文字链接，但稍加修改也可以用于图像文本链接，
                //因为图像文件链接只在头部多个！号而已。

                var edit = GetOpenedEditor(file.FullName);
                string[] lines;
                if (edit != null)
                {
                    //打开的文件，以正在编辑的内容为准
                    lines = edit.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.None);
                }
                else
                {
                    //没打开的文件，以磁盘数据为准
                    lines = File.ReadAllLines(file.FullName);
                }

                var lineNumber = 0;
                foreach (var line in lines)
                {
                    lineNumber++;
                    Regex regex = new Regex(@"!?\[.*\](.*)");
                    var match = regex.Match(line);
                    if (match.Success)
                    {
                        var destLine = match.Value.ToLower();
                        var srcLine = linkText.ToLower();

                        bool isLinked = destLine.Contains(srcLine);
                        if (isLinked == false)
                        {
                            if (destLine.Contains("](./"))
                            {
                                if (srcLine.StartsWith("(./") == false)
                                {
                                    srcLine = "(./" + srcLine.Substring(1);
                                    isLinked = destLine.Contains(srcLine);
                                }
                            }
                            else
                            {
                                if (srcLine.StartsWith("(./"))
                                {
                                    srcLine = "(" + srcLine.Substring(3);
                                    isLinked = destLine.Contains(srcLine);
                                }
                            }
                        }

                        if (isLinked)
                        {
                            item.Items.Add(new FindLineTreeViewItem(file.FullName, file.Name, lineNumber, match.Index, match.Length,
                                $"文件：{file.Name}，第 {lineNumber} 行", null, FindLineTreeViewItem.ItemType.Normal));
                        }
                    }
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            foreach (var subDirectory in subDirectories)
            {
                if (subDirectory.FullName.EndsWith("~")) continue;

                FindAllLinksToThisFile(item, srcPath, subDirectory.FullName, subDirectory.Name);
            }
        }

        /// <summary>
        /// 取引用链接文本。
        /// </summary>
        /// <param name="resourceFullPathName">是指将被引用的资源的文件路径</param>
        /// <param name="mdFileFullPathName">resourceFullPathName。</param>
        /// <param name="shortName">资源短名，通常是文件名。</param>
        /// <param name="onlyPath">为true时只返回路径；默认情况下为假，返回完整的 Markdown 格式的链接文本。</param>
        /// <returns>生成的用于插入到<para>mdFileFullPathName</para>指向的Md文件中的链接的文本。</returns>
        internal string BuildLinkText(string resourceFullPathName, string mdFileFullPathName, ref string shortName, bool onlyPath = false)
        {
            //if (resourceFullPathName != null)
            //{
            //    resourceFullPathName = resourceFullPathName.ToLower();
            //}

            //if (mdFileFullPathName != null)
            //{
            //    mdFileFullPathName = mdFileFullPathName.ToLower();
            //}

            if (resourceFullPathName.EndsWith(".md"))
            {
                resourceFullPathName = resourceFullPathName.Substring(0, resourceFullPathName.Length - 3) + ".html";
            }

            var workspacePath = Globals.PathOfWorkspace.ToLower();

            //思路：先找出共同的、最接近的祖先级目录
            var separator = new char[] { '\\', '/' };
            string[] src = resourceFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            string[] dest = mdFileFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);

            var minIndex = Math.Min(src.Length, dest.Length) - 1;

            if (minIndex < 0) return "";

            int aIndex = -1;
            for (int i = 0; i <= minIndex; i++)
            {
                if (src[i].ToLower() != dest[i].ToLower())
                {
                    aIndex = i;
                    break;
                }
            }

            //生成相对路径的前半部分，类似（../../）这样子。
            string[] file = mdFileFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            string header;
            if (resourceFullPathName == mdFileFullPathName || aIndex < 0)//同一文件内部引用
            {
                header = "";
            }
            else
            {
                header = FindLineTreeViewItem.BuildHtmlRefText(file.Length - aIndex - 1);

                for (int i = aIndex; i < src.Length; i++)
                {
                    header += src[i];
                    header += "/";
                }

                if (header.EndsWith(".md"))
                {
                    header = header.Substring(0, header.Length - 3) + ".html";
                }

                if (header.EndsWith("/"))
                {
                    header = header.Substring(0, header.Length - 1);
                }
            }

            if (shortName.EndsWith(".md"))
            {
                shortName = shortName.Substring(0, shortName.Length - 3);
            }

            var linkText = "[" + FormatDocumentTitle(shortName) + "](" + header + ")";
            return onlyPath ? header : linkText;
        }

        /// <summary>
        /// 在工作区管理器中创建子目录。
        /// </summary>
        private void miNewDirectory_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中要在哪个目录下新建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (Directory.Exists(wtvi.FullPath) == false)
            {
                LMessageBox.Show("只能在目录下新建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
            if (di.Name.EndsWith("~"))
            {
                LMessageBox.Show("以波形符结尾的目录是程序自动管理的资源目录，不允许在其下新建子目录。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var newShortDirectoryName = InputBox.Show(Globals.AppName, $"◆ {wtvi.Title}\r\n　◇ ..新目录..\r\n\r\n　　 请输入新【下级】目录名（不能以“_”或“.”开头）：", "", true);

            var newShortCommentName = newShortDirectoryName;

            newShortDirectoryName = ChinesePinYin.ToChinesePinYinText(newShortCommentName);

            if (string.IsNullOrEmpty(newShortDirectoryName))
            {
                //LMessageBox.Show("目录名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (AskEnglishFileName)
            {
                var regValidFileNameChars = new Regex(@"^[a-zA-Z0-9_][a-zA-Z0-9\-~_]{0,}");
                if (regValidFileNameChars.IsMatch(newShortCommentName) == false)
                {
                    // 为什么要用英文文件名？
                    // 这是因为 CHM 对中文路径的支持有问题——很多网络设备对中文路径的支持也有问题。
                    newShortDirectoryName = InputBox.Show(Globals.AppName, $"您输入的【{newShortCommentName}】将被用作目录元文件的标题。\r\n\r\n由于含有中文或特殊字符，建议使用下列字符串作目录名：",
                        newShortDirectoryName, true, "说明：\r\n　　⑴建议的文件名是取中文拼音首字母生成的。\r\n　　⑵您可以在这里改成有意义的英文字符串。\r\n　　⑶空白字符会被替换为连字符。", false, true);
                    if (string.IsNullOrWhiteSpace(newShortDirectoryName)) return;  // 用户放弃新建目录。
                }
            }

            newShortDirectoryName = ChinesePinYin.ToChinesePinYinText(newShortDirectoryName.Replace(" ", "-").Replace("　", "-").Replace("\t", "-"));  // 防止用户仍然不慎输入了中文。

            try
            {
                var newFullPath = (wtvi.FullPath.EndsWith("\\") ? wtvi.FullPath : (wtvi.FullPath + "\\")) + newShortDirectoryName;
                if (Directory.Exists(newFullPath) == false)
                {
                    Directory.CreateDirectory(newFullPath);
                    var destDirectoryInfo = new DirectoryInfo(newFullPath);

                    //自动创建目录元文件，不必再双击了。

                    Regex headerNumberRegex = new Regex(@"^[lL]\d{7,}[ 　-]");
                    var matchHeaderNumber = headerNumberRegex.Match(newShortCommentName);
                    if (matchHeaderNumber.Success)
                    {
                        newShortCommentName = newShortCommentName.Substring(matchHeaderNumber.Length);
                    }

                    using (var stream = File.CreateText(destDirectoryInfo.FullName + "\\" + "_" + destDirectoryInfo.Name + ".md"))
                    {
                        stream.Write($"\r\n%{/*FormatDocumentTitle(*/newShortCommentName/*)*/}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n{Properties.Resources.metaFileInlineEnviromentTextSpan}\r\n\r\n");
                    }
                }
                else
                {
                    LMessageBox.Show("已存在同名目录，操作无法完成。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                var newItem = new WorkspaceTreeViewItem(newFullPath, Globals.MainWindow);
                wtvi.Items.Add(newItem);
                if (wtvi.IsExpanded == false) wtvi.IsExpanded = true;

                //2017年7月21日，现在由用户决定插入位置，不再自动排序。
                //插入同级节点时，直接添加到当前节点下一个位置；插入下级节点，直接添加到下级的最后一个位置。
                #region 废弃代码
                ////解决索引问题，判断在哪里插入。
                //List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                //int baseIndex = 0;
                //foreach (var item in wtvi.Items)
                //{
                //    var wi = item as WorkspaceTreeViewItem;
                //    if (wi != null)
                //    {
                //        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                //        {
                //            baseIndex++;
                //            continue;
                //        }
                //        tmpItems.Add(wi);
                //    }
                //}
                //tmpItems.Add(newItem);
                //tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                //var index = tmpItems.IndexOf(newItem) + baseIndex;
                //wtvi.Items.Insert(index, newItem); 
                #endregion

                newItem.IsSelected = true;
                workspaceManager.SaveWorkspaceTreeviewToXml();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("创建目录失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);

            }
        }

        /// <summary>
        /// 切换活动文档时自动对焦，以便直接输入文本。
        /// </summary>
        private void mainTabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (mainTabControl.SelectedIndex < 0)
            {
                cmbSearchArea2.SelectedIndex = 2;
                cmbSearchArea.SelectedIndex = 2;
            }

            RefreshTextInfos();

            //所有文档都关闭后，自动退出全屏状态
            if (this.mainTabControl.Items.Count <= 0)
            {
                if (cmbPerspective.SelectedIndex == (int)Perspective.FullScreenPreview ||
                    cmbPerspective.SelectedIndex == (int)Perspective.FullScreenEditing)
                {
                    cmbPerspective.SelectedIndex = (int)Perspective.Normal;
                }
            }

            var activeEdit = this.ActivedEditor;
            if (activeEdit != null)
            {
                var destWorkspaceTreeViewItem = FindWorkspaceTreeViewItem(activeEdit.FullFilePath);
                if (destWorkspaceTreeViewItem != null)
                {
                    destWorkspaceTreeViewItem.IsSelected = true;
                    var parentItem = destWorkspaceTreeViewItem.ParentWorkspaceTreeViewItem;
                    while (parentItem != null)
                    {
                        parentItem.IsExpanded = true;
                        parentItem = parentItem.ParentWorkspaceTreeViewItem;
                    }
                }

                activeEdit.EditorBase.Focus();
            }

            RefreshTextAutoWrapToStatusBar();
        }

        /// <summary>
        /// 选择主题来编译 Html 文件。
        /// </summary>
        private void cmbColor_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (App.WorkspaceConfigManager == null || cmbColor.SelectedItem == null) return;

            var colorName = (cmbColor.SelectedItem as ComboBoxItem).Tag.ToString();
            App.WorkspaceConfigManager.Set("color", colorName);
            //之前不能在这里设置，会导致循环设置、死锁。目前一切正常。

            if (colorName == "light")
            {
                imagePreviewOutBorder.Background = Brushes.White;
                tbImageTitle.Foreground = Brushes.Black;
            }
            else if (colorName == "dark")
            {
                imagePreviewOutBorder.Background = new SolidColorBrush(Color.FromRgb(0x3D, 0x3D, 0x3D));
                tbImageTitle.Foreground = Brushes.White;
            }
            else
            {
                imagePreviewOutBorder.Background = Brushes.Transparent;
                tbImageTitle.Foreground = Brushes.Black;
            }

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "Theme",
                HtmlOptionText = "配色主题",
            });
        }

        /// <summary>
        /// 在工作区管理器中重命名文件或文件夹。
        /// </summary>
        private void miRename_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中要重命名的项目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            var result = LMessageBox.Show("重命名文件（或目录）会导致所有对此文件（或目录）的引用（链接）失效。\r\n　　" +
                "> 对与此文件相关的资源文件的引用也会失效。\r\n\r\n" +
                "　　建议先使用“查找引用”功能看看此文件（或此目录）是否被引用过。\r\n\r\n" +
                "　　要自动查找一下可能存在的引用么？（要注意可能出现同名文件被引用！）",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_WarningBeforeRename");
            //NPA_ means No Prompt Again.

            if (result == MessageBoxResult.Yes)
            {
                cmbFindText.Text = wtvi.ShortName;
                cmbSearchArea.SelectedIndex = 2;
                FindText(tvFindAndReplace);
                return;
            }

            if (wtvi.IsDirectoryExists)
            {
                if (wtvi.FullPath == Globals.PathOfWorkspace)
                {
                    LMessageBox.Show("不能重命名工作区目录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
                {
                    LMessageBox.Show("这是程序预定的资源目录名，不可修改！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var newShortName = InputBox.Show(Globals.AppName, "请输入新目录名称（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true, "◆说明：文章标题须在对应文件中修改！");
                if (newShortName == null) return;  // 用户取消操作。

                if (newShortName.Trim(new char[] { }) == "")
                {
                    LMessageBox.Show("指定的目录名不能全部是空白字符。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                // 注意：不能对 newFullPath 使用拼音首字母转换，会造成路径错误！！
                newShortName = ChinesePinYin.ToChinesePinYinText(newShortName);

                try
                {
                    #region 在重命名一个目录之前，先将该目录对应的元文件、元文件资源文件夹、编译后的html元文件重命名。
                    var oldFullPath = wtvi.FullPath;
                    if (oldFullPath.EndsWith("\\") == false) oldFullPath += "\\";

                    var newFullPath = wtvi.FullPath;
                    if (newFullPath.EndsWith("\\")) newFullPath = newFullPath.Substring(0, newFullPath.Length - 1);

                    string oldShortName = "";
                    var lastindex = newFullPath.LastIndexOf("\\");
                    if (lastindex >= 0)
                    {
                        oldShortName = newFullPath.Substring(lastindex + 1);
                        newFullPath = newFullPath.Substring(0, lastindex);
                    }

                    newFullPath = newFullPath + "\\" + newShortName + "\\";
                    if (Directory.Exists(newFullPath))
                    {
                        LMessageBox.Show("已存在指定名称的目录，操作无法完成。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    var oldMetaFileShortName = "_" + oldShortName + ".md";
                    var oldMetaFileFullPath = oldFullPath + oldMetaFileShortName;
                    var oldMetaFileResourceDirectoryPath = oldFullPath + "_" + oldShortName + "~\\";
                    var oldMetaHtmlFileFullPath = oldFullPath + "_" + oldShortName + ".html";

                    if (File.Exists(oldMetaFileFullPath))
                        File.Move(oldMetaFileFullPath, oldFullPath + "_" + newShortName + ".md");

                    if (Directory.Exists(oldMetaFileResourceDirectoryPath))
                        Directory.Move(oldMetaFileResourceDirectoryPath, oldFullPath + "_" + newShortName + "~");

                    if (File.Exists(oldMetaHtmlFileFullPath))
                        File.Move(oldMetaHtmlFileFullPath, oldFullPath + "_" + newShortName + ".html");

                    #endregion

                    //然后再命名文件夹本身。
                    Directory.Move(wtvi.FullPath, newFullPath);
                    //wtvi.FullPath = newFullPath;
                    ReNameFolder(wtvi, oldFullPath, newFullPath);
                    wtvi.RefreshFileState();

                    //重命名打开的元文件。
                    foreach (var ue in this.mainTabControl.Items)
                    {
                        MarkdownEditor editor = ue as MarkdownEditor;
                        if (editor == null || editor.FullFilePath == null) continue;

                        if (editor.FullFilePath.ToLower().StartsWith(oldFullPath.ToLower()))
                        {
                            var shortName = editor.ShortFileName;
                            if (shortName == null) continue;
                            if (shortName.StartsWith("_"))
                            {
                                //元文件，要特别处理
                                if (oldFullPath.EndsWith("\\")) oldFullPath = oldFullPath.Substring(0, oldFullPath.Length - 1);
                                var lastI = oldFullPath.LastIndexOf("\\");
                                if (lastI >= 0)
                                {
                                    var s = oldFullPath.Substring(lastI + 1);
                                    if (("_" + s + ".md").ToLower() == shortName.ToLower())
                                    {
                                        editor.FullFilePath = newFullPath + "_" + newShortName + ".md";
                                        continue;
                                    }
                                }
                            }

                            editor.FullFilePath = newFullPath + editor.FullFilePath.Substring(oldFullPath.Length);
                        }
                    }

                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }
            else if (wtvi.IsImageFileExist)
            {
                var newShortCommentName = InputBox.Show(Globals.AppName, "请输入新图像文件名（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true, "注意：不要保留后缀名。");
                if (string.IsNullOrWhiteSpace(newShortCommentName)) return;

                if (newShortCommentName.StartsWith("."))
                {
                    LMessageBox.Show("指定的文件名不能为空，也不能以句号开头。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var oldFileInfo = new FileInfo(wtvi.FullPath);
                var oldExtension = oldFileInfo.Extension;

                // 注意：不能对 newFullPath 使用拼音首字母转换，会造成路径错误！！
                var newShortName = ChinesePinYin.ToChinesePinYinText(newShortCommentName) + oldExtension;

                var newFullPath = oldFileInfo.DirectoryName + "\\" + newShortName;

                if (File.Exists(newFullPath))
                {
                    LMessageBox.Show("已存在指定名称的文件，无法完成操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    File.Move(wtvi.FullPath, newFullPath);
                    wtvi.FullPath = newFullPath;

                    //写注释文件行
                    var commentTextFilePath = oldFileInfo.DirectoryName + "\\~.txt";
                    if (File.Exists(commentTextFilePath))
                    {
                        using (var stream = File.AppendText(commentTextFilePath))
                        {
                            stream.WriteLine($"{newShortName}|{newShortCommentName}{oldExtension}");
                        }
                    }
                    else
                    {
                        using (var stream = File.CreateText(commentTextFilePath))
                        {
                            stream.WriteLine($"{newShortName}|{newShortCommentName}{oldExtension}");
                        }
                    }

                    wtvi.RefreshFileState();//要在写注释文件之后，否则取不出标题文本。

                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }
            else if (wtvi.IsSoundFileExist)
            {
                var newShortCommentName = InputBox.Show(Globals.AppName, "请输入新音频文件名（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true, "注意：不要保留后缀名。");
                if (string.IsNullOrWhiteSpace(newShortCommentName)) return;

                if (newShortCommentName.StartsWith("."))
                {
                    LMessageBox.Show("指定的文件名不能为空，也不能以句号开头。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var oldFileInfo = new FileInfo(wtvi.FullPath);
                var oldExtension = oldFileInfo.Extension;

                var newShortName = ChinesePinYin.ToChinesePinYinText(newShortCommentName) + oldExtension;

                var newFullPath = oldFileInfo.DirectoryName + "\\" + newShortName;

                if (File.Exists(newFullPath))
                {
                    LMessageBox.Show("已存在指定名称的文件，无法完成操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    File.Move(wtvi.FullPath, newFullPath);
                    wtvi.FullPath = newFullPath;

                    //写注释文件行
                    var commentTextFilePath = oldFileInfo.DirectoryName + "\\~.txt";
                    if (File.Exists(commentTextFilePath))
                    {
                        using (var stream = File.AppendText(commentTextFilePath))
                        {
                            stream.WriteLine($"{newShortName}|{newShortCommentName}{oldExtension}");
                        }
                    }
                    else
                    {
                        using (var stream = File.CreateText(commentTextFilePath))
                        {
                            stream.WriteLine($"{newShortName}|{newShortCommentName}{oldExtension}");
                        }
                    }

                    wtvi.RefreshFileState();  //要在写注释文件之后，否则取不出标题文本。
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }
            else if (wtvi.IsFileExists)
            {
                var newShortName = InputBox.Show(Globals.AppName, "请输入新文件名（不能以“_”开头，尽量以字母开头）：", wtvi.ShortName, true);
                if (string.IsNullOrWhiteSpace(newShortName)) return;

                if (newShortName.ToLower() == ".md")
                {
                    LMessageBox.Show("指定的文件名不能为空。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (newShortName.ToLower().EndsWith(".md"))
                {
                    newShortName = newShortName.Substring(0, newShortName.Length - 3);
                }

                newShortName = ChinesePinYin.ToChinesePinYinText(newShortName);

                var newFullPath = wtvi.FullPath;
                if (newFullPath.EndsWith("\\")) newFullPath = newFullPath.Substring(0, newFullPath.Length - 1);

                var lastindex = newFullPath.LastIndexOf("\\");
                if (lastindex >= 0) newFullPath = newFullPath.Substring(0, lastindex);

                newFullPath = newFullPath + "\\" + newShortName;

                if (File.Exists(newFullPath))
                {
                    LMessageBox.Show("已存在指定名称的文件，无法完成操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    if (wtvi.IsMarkdownFilePath && newFullPath.ToLower().EndsWith(".md") == false) newFullPath += ".md";

                    File.Move(wtvi.FullPath, newFullPath);
                    var oldShortName = wtvi.ShortName;

                    var oldFilePath = wtvi.FullPath;
                    wtvi.FullPath = newFullPath;
                    wtvi.RefreshFileState();

                    //更新打开的文档的对应编辑器的FullPath
                    foreach (var item in Globals.MainWindow.mainTabControl.Items)
                    {
                        MarkdownEditor me = item as MarkdownEditor;
                        if (me == null) continue;

                        if (me.FullFilePath == oldFilePath)
                        {
                            me.FullFilePath = newFullPath;
                            break;
                        }
                    }

                    if (wtvi.FullPath.ToLower().EndsWith(".md"))
                    {
                        var parentDirectoryPath = wtvi.FullPath.Substring(0, wtvi.FullPath.LastIndexOf("\\"));
                        if (parentDirectoryPath.EndsWith("\\") == false) parentDirectoryPath += "\\";

                        var directories = new DirectoryInfo(parentDirectoryPath).GetDirectories();
                        foreach (var directory in directories)
                        {
                            var directoryShortName = oldShortName.Substring(0, oldShortName.Length - 3);
                            if (directory.FullName.EndsWith(directoryShortName) || directory.FullName.EndsWith(directoryShortName + "~"))
                            {
                                var dest = directory.FullName;
                                if (dest.EndsWith("\\")) dest.Substring(0, dest.Length - 1);

                                int lastIndex = dest.LastIndexOf("\\");
                                dest = dest.Substring(0, lastindex + 1);

                                directory.MoveTo(dest + newShortName + "~\\");
                                break;
                            }
                        }

                        var htmlFilePath = oldFilePath.Substring(0, oldFilePath.Length - 3) + ".html";

                        if (File.Exists(htmlFilePath))
                        {
                            File.Copy(htmlFilePath, parentDirectoryPath + newShortName + ".html", true);
                        }
                    }
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }

            //workspaceManager.Refresh(wtvi.FullPath);
            //wtvi.RefreshFileState();
            //wtvi.FullPath 赋值时会自动调用上一行。

            //2017年7月21日，现在改由用户决定位置，此代码已不必存在，保留备查。
            #region 废弃代码
            //位置排序
            //var parentItem = wtvi.Parent as WorkspaceTreeViewItem;
            //if (parentItem != null)
            //{
            //    List<WorkspaceTreeViewItem> sortList = new List<LunarMarkdownEditor.WorkspaceTreeViewItem>();
            //    foreach (var item in parentItem.Items)
            //    {
            //        var ti = item as WorkspaceTreeViewItem;
            //        if (ti == null) continue;
            //        sortList.Add(ti);
            //    }

            //    var oldIndex = sortList.IndexOf(wtvi);

            //    sortList.Sort(new WorkspaceTreeViewItemCompare());

            //    var newIndex = sortList.IndexOf(wtvi);
            //    if (newIndex >= 0 && oldIndex >= 0)
            //    {
            //        parentItem.Items.Remove(wtvi);
            //        parentItem.Items.Insert(newIndex, wtvi);
            //    }
            //}
            #endregion

            wtvi.IsSelected = true;
        }

        /// <summary>
        /// 仅用于重命名一个文件夹后，刷新其下级项目的 FullPath。
        /// </summary>
        /// <param name="wtvi"></param>
        /// <param name="oldFullPathOfFolder"></param>
        /// <param name="newFullPathOfFolder"></param>
        private void ReNameFolder(WorkspaceTreeViewItem wtvi, string oldFullPathOfFolder, string newFullPathOfFolder)
        {
            if (wtvi == null || string.IsNullOrWhiteSpace(newFullPathOfFolder) || string.IsNullOrWhiteSpace(oldFullPathOfFolder)) return;

            wtvi.FullPath = newFullPathOfFolder;

            if (wtvi.Items.Count <= 0) return;

            foreach (var item in wtvi.Items)
            {
                var subItem = item as WorkspaceTreeViewItem;
                if (subItem == null) continue;

                if (subItem.FullPath.ToLower().StartsWith(oldFullPathOfFolder.ToLower()))
                {
                    subItem.FullPath = newFullPathOfFolder + subItem.FullPath.Substring(oldFullPathOfFolder.Length);
                }

                if (subItem.Items.Count > 0)
                {
                    ReNameFolder(subItem, oldFullPathOfFolder, newFullPathOfFolder);
                }
            }
        }

        /// <summary>
        /// 工作区管理器的快捷键处理。
        /// </summary>
        private void tvWorkDirectory_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Enter:
                    {
                        if (tvWorkDirectory.SelectedItem != null)
                        {
                            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
                            if (wtvi != null)
                            {
                                if (wtvi.IsMarkdownFilePath)
                                {
                                    wtvi.OpenFile();
                                    e.Handled = true;
                                }
                                else if (wtvi.IsDirectoryExists)
                                {
                                    wtvi.IsExpanded = true;
                                    e.Handled = true;
                                }
                                else if (wtvi.IsImageFileExist)
                                {
                                    wtvi.InsertImageTagToDocument(wtvi.FullPath);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.F2:
                    {
                        miRename_Click(sender, e);
                        break;
                    }
            }
        }

        /// <summary>
        /// 格式化活动编辑器中的 Markdown 文本。
        /// </summary>
        private void miFormatDocument_Click(object sender, RoutedEventArgs e)
        {
            Format();
        }

        /// <summary>
        /// 格式化活动编辑器中的 Markdown 文本。
        /// </summary>
        private void Format()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Format();
        }

        /// <summary>
        /// 根据路径查找工作区管理器中有无此条目。
        /// </summary>
        /// <param name="fullPath">要找的文件或目录的路径。</param>
        /// <returns>返回工作区管理器中某符合条件的条目。</returns>
        public WorkspaceTreeViewItem FindWorkspaceTreeViewItem(string fullPath)
        {
            if (string.IsNullOrEmpty(fullPath)) return null;

            if (IsMetaFilePath(fullPath))
            {
                FileInfo fi = new FileInfo(fullPath);
                fullPath = fi.DirectoryName;
                if (fullPath.EndsWith("\\") == false)
                {
                    fullPath += "\\";
                }
            }

            var baseItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;//根节点，必然存在。

            if (baseItem.FullPath.ToLower() == fullPath.ToLower()) return baseItem;

            foreach (var item in baseItem.Items)
            {
                WorkspaceTreeViewItem wtvi = item as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                if (wtvi.FullPath.ToLower() == fullPath.ToLower()) return wtvi;

                var resultItem = SearchWorkspaceTreeViewItem(wtvi, fullPath);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 向工作区管理器中添加一个新条目。
        /// </summary>
        /// <param name="parentPath">基准位置父条目的“文件或文件夹路径”。</param>
        /// <param name="subPath">要添加的子文件或目录的路径。</param>
        public void InsertSubWorkspaceTreeViewItem(string parentPath, string subPath)
        {
            var parentItem = FindWorkspaceTreeViewItem(parentPath);

            if (parentItem == null)
            {
                if (parentPath.EndsWith("~") || parentPath.EndsWith("~\\"))
                {
                    //向资源文件夹条目下添加新资源子条目
                    DirectoryInfo parentDirectory = new DirectoryInfo(parentPath);
                    if (Directory.Exists(parentPath) == false)
                    {
                        parentDirectory = Directory.CreateDirectory(parentPath);
                    }

                    var preParent = parentDirectory.Parent;  //带MD元文件的目录条目、或者MD文件条目
                    var prePreParent = preParent.Parent;

                    if (preParent != null && preParent.Exists && prePreParent != null && prePreParent.Exists)
                    {
                        if (preParent.Name == "_" + prePreParent.Name + "~")
                        {
                            //目录元文件的资源目录
                            var prePreparentPath = prePreParent.FullName;
                            if (prePreparentPath.EndsWith("\\") == false)
                                prePreparentPath += "\\";
                            var prePreParentItem = FindWorkspaceTreeViewItem(prePreparentPath);
                            if (prePreParentItem != null)
                            {
                                WorkspaceTreeViewItem resourceItem = new LunarMarkdownEditor.WorkspaceTreeViewItem(parentPath, Globals.MainWindow);

                                #region 根据情况自动添加三类资源文件夹条目
                                if (resourceItem.IsImageResourceDirectory)
                                {
                                    prePreParentItem.Items.Insert(0, resourceItem);
                                }
                                else if (resourceItem.IsSoundResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broImageResourceItem = null;
                                    foreach (var item in prePreParentItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsImageResourceDirectory) { broImageResourceItem = wi; break; }
                                    }

                                    if (broImageResourceItem == null)
                                    {
                                        //没有 图像~ 资源文件夹条目，则声音在第0位置。
                                        prePreParentItem.Items.Insert(0, resourceItem);
                                    }
                                    else
                                    {
                                        //如果存在 图像~ 资源文件夹条目，放在它后面一个位置。
                                        prePreParentItem.Items.Insert(prePreParentItem.Items.IndexOf(broImageResourceItem) + 1, resourceItem);
                                    }
                                }
                                else if (resourceItem.IsVedioResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broSoundResourceItem = null;
                                    foreach (var item in prePreParentItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsSoundResourceDirectory) { broSoundResourceItem = wi; break; }
                                    }

                                    if (broSoundResourceItem == null)
                                    {
                                        //没有 声音~ 资源文件夹条目，还要看看是否有 图像~ 资源文件夹
                                        WorkspaceTreeViewItem broImageResourceItem = null;
                                        foreach (var item in prePreParentItem.Items)
                                        {
                                            var wi = item as WorkspaceTreeViewItem;
                                            if (wi == null) continue;

                                            if (wi.IsImageResourceDirectory) { broImageResourceItem = wi; break; }
                                        }

                                        if (broImageResourceItem == null)
                                        {
                                            //既没有 声音~ 又没有 图像~ 资源文件夹条目，则视频在第0位置。
                                            prePreParentItem.Items.Insert(0, resourceItem);
                                        }
                                        else
                                        {
                                            //如果没有 声音~ 但存在 图像~ 资源文件夹条目，放在 图像~ 后面一个位置。
                                            prePreParentItem.Items.Insert(prePreParentItem.Items.IndexOf(broImageResourceItem) + 1, resourceItem);
                                        }
                                    }
                                    else
                                    {
                                        //如果存在 声音~ 资源文件夹条目，放在它后面一个位置。
                                        prePreParentItem.Items.Insert(prePreParentItem.Items.IndexOf(broSoundResourceItem) + 1, resourceItem);
                                    }
                                }
                                else
                                {
                                    prePreParentItem.Items.Insert(0, resourceItem);
                                }
                                #endregion 根据情况自动添加三类资源文件夹条目

                                parentItem = resourceItem;
                            }
                        }
                        else
                        {
                            //普通文件的资源目录
                            //Images~、Sounds~、Vedios~目录的上级是资源文件目录（即由 MD文件短名~ 构成）；
                            var newSubResourceItem = new WorkspaceTreeViewItem(subPath, this);
                            var subResourceFolderItem = FindWorkspaceTreeViewItem(parentPath);
                            if (subResourceFolderItem == null)
                            {
                                var mdFilePath = parentDirectory.Parent.FullName.Substring(0, parentDirectory.Parent.FullName.Length - 1) + ".md";
                                var mdFileItem = FindWorkspaceTreeViewItem(mdFilePath);

                                subResourceFolderItem = new LunarMarkdownEditor.WorkspaceTreeViewItem(parentPath, this);

                                //mdFileItem.Items.Add(subResourceItem);

                                #region 根据情况自动添加三类资源文件夹条目
                                if (subResourceFolderItem.IsImageResourceDirectory)
                                {
                                    mdFileItem.Items.Insert(0, subResourceFolderItem);
                                }
                                else if (subResourceFolderItem.IsSoundResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broImagenewSubResourceItem = null;
                                    foreach (var item in mdFileItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsImageResourceDirectory) { broImagenewSubResourceItem = wi; break; }
                                    }

                                    if (broImagenewSubResourceItem == null)
                                    {
                                        //没有 图像~ 资源文件夹条目，则声音在第0位置。
                                        mdFileItem.Items.Insert(0, subResourceFolderItem);
                                    }
                                    else
                                    {
                                        //如果存在 图像~ 资源文件夹条目，放在它后面一个位置。
                                        mdFileItem.Items.Insert(mdFileItem.Items.IndexOf(broImagenewSubResourceItem) + 1, subResourceFolderItem);
                                    }
                                }
                                else if (subResourceFolderItem.IsVedioResourceDirectory)
                                {
                                    WorkspaceTreeViewItem broSoundnewSubResourceItem = null;
                                    foreach (var item in mdFileItem.Items)
                                    {
                                        var wi = item as WorkspaceTreeViewItem;
                                        if (wi == null) continue;

                                        if (wi.IsSoundResourceDirectory) { broSoundnewSubResourceItem = wi; break; }
                                    }

                                    if (broSoundnewSubResourceItem == null)
                                    {
                                        //没有 声音~ 资源文件夹条目，还要看看是否有 图像~ 资源文件夹
                                        WorkspaceTreeViewItem broImagenewSubResourceItem = null;
                                        foreach (var item in mdFileItem.Items)
                                        {
                                            var wi = item as WorkspaceTreeViewItem;
                                            if (wi == null) continue;

                                            if (wi.IsImageResourceDirectory) { broImagenewSubResourceItem = wi; break; }
                                        }

                                        if (broImagenewSubResourceItem == null)
                                        {
                                            //既没有 声音~ 又没有 图像~ 资源文件夹条目，则视频在第0位置。
                                            mdFileItem.Items.Insert(0, subResourceFolderItem);
                                        }
                                        else
                                        {
                                            //如果没有 声音~ 但存在 图像~ 资源文件夹条目，放在 图像~ 后面一个位置。
                                            mdFileItem.Items.Insert(mdFileItem.Items.IndexOf(broImagenewSubResourceItem) + 1, subResourceFolderItem);
                                        }
                                    }
                                    else
                                    {
                                        //如果存在 声音~ 资源文件夹条目，放在它后面一个位置。
                                        mdFileItem.Items.Insert(mdFileItem.Items.IndexOf(broSoundnewSubResourceItem) + 1, subResourceFolderItem);
                                    }
                                }
                                else
                                {
                                    mdFileItem.Items.Insert(0, subResourceFolderItem);
                                }
                                #endregion 根据情况自动添加三类资源文件夹条目
                            }

                            subResourceFolderItem.Items.Add(newSubResourceItem);

                        }
                    }
                }
            }

            if (parentItem == null) return;

            if (parentItem.IsSubWorkspaceTreeViewItemExists(subPath)) return;

            List<WorkspaceTreeViewItem> list = new List<WorkspaceTreeViewItem>();
            foreach (var item in parentItem.Items)
            {
                var wvItem = item as WorkspaceTreeViewItem;
                if (wvItem != null) list.Add(wvItem);
            }

            var newSubItem = new WorkspaceTreeViewItem(subPath, this);
            list.Add(newSubItem);
            list.Sort(new WorkspaceTreeViewItemCompare());

            var newIndex = list.IndexOf(newSubItem);
            parentItem.Items.Insert(newIndex, newSubItem);
        }

        /// <summary>
        /// 向工作区管理器中的某个条目下添加一个子级条目。
        /// </summary>
        /// <param name="parentItem">父条目。</param>
        /// <param name="subItem">子条目。</param>
        public void InsertSubWorkspaceTreeViewItem(WorkspaceTreeViewItem parentItem, WorkspaceTreeViewItem subItem)
        {
            if (parentItem == null || subItem == null) return;

            if (parentItem.Items.Contains(subItem)) return;
            if (parentItem.IsSubWorkspaceTreeViewItemExists(subItem.FullPath)) return;

            List<WorkspaceTreeViewItem> list = new List<WorkspaceTreeViewItem>();
            foreach (var item in parentItem.Items)
            {
                var wvItem = item as WorkspaceTreeViewItem;
                if (wvItem != null) list.Add(wvItem);
            }

            list.Add(subItem);
            list.Sort(new WorkspaceTreeViewItemCompare());

            var newIndex = list.IndexOf(subItem);
            parentItem.Items.Insert(newIndex, subItem);
        }

        /// <summary>
        /// 判断是否目录元文件。
        /// </summary>
        /// <param name="fullPath">文件路径。</param>
        /// <returns>以“_”开头的“.md”文件即返回真。</returns>
        private bool IsMetaFilePath(string fullPath)
        {
            try
            {
                FileInfo fi = new FileInfo(fullPath);
                if (fi.Name.StartsWith("_") && fi.Name.ToLower().EndsWith(".md"))
                {
                    return true;
                }

                return false;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 根据指定路径查找工作区管理器中对应的条目。
        /// </summary>
        /// <param name="item">从此条目开始查找。</param>
        /// <param name="fullPath">要查找的条目指向的磁盘路径。</param>
        /// <returns>返回找到的条目。</returns>
        private WorkspaceTreeViewItem SearchWorkspaceTreeViewItem(WorkspaceTreeViewItem item, string fullPath)
        {
            if (item == null || string.IsNullOrEmpty(fullPath)) return null;

            if (item.FullPath.ToLower() == fullPath.ToLower()) return item;

            foreach (var childItem in item.Items)
            {
                WorkspaceTreeViewItem wtvi = childItem as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                var resultItem = SearchWorkspaceTreeViewItem(wtvi, fullPath);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 根据指定的标题查找是否存在对应的项。
        /// </summary>
        /// <param name="title">标题文本。</param>
        /// <returns>如果没有相应标题，返回null。</returns>
        public WorkspaceTreeViewItem FindWorkspaceTreeViewItemByTitle(string title)
        {
            if (string.IsNullOrEmpty(title)) return null;

            var baseItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;//根节点，必然存在。

            if (baseItem.Title.ToLower() == title.ToLower()) return baseItem;

            foreach (var item in baseItem.Items)
            {
                WorkspaceTreeViewItem wtvi = item as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                if (wtvi.Title.ToLower() == title.ToLower()) return wtvi;

                var resultItem = SearchWorkspaceTreeViewItemByTitle(wtvi, title);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 根据指定标题查找工作区管理器中对应的条目。
        /// </summary>
        /// <param name="item">从此条目开始查找。</param>
        /// <param name="title">要查找的条目指向的磁盘路径。</param>
        /// <returns>返回找到的条目。</returns>
        private WorkspaceTreeViewItem SearchWorkspaceTreeViewItemByTitle(WorkspaceTreeViewItem item, string title)
        {
            if (item == null || string.IsNullOrEmpty(title)) return null;

            if (item.Title.ToLower() == title.ToLower()) return item;

            foreach (var childItem in item.Items)
            {
                WorkspaceTreeViewItem wtvi = childItem as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                var resultItem = SearchWorkspaceTreeViewItemByTitle(wtvi, title);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 格式化当前活动编辑器当前编辑区附近的文字表。
        /// </summary>
        private void miFormatTextTable_Click(object sender, RoutedEventArgs e)
        {
            FormatAsTextTable();
        }

        /// <summary>
        /// 格式化当前活动编辑器当前编辑区附近的文字表。
        /// </summary>
        private void FormatAsTextTable()
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.FormatTextTable();
        }

        /// <summary>
        /// 读取工作区设置的Html编译选项。
        /// </summary>
        private void LoadWorkspaceHtmlCompileOptions()
        {
            //是否启用任务列表项、二维文字表单元格、冒号开头的注释、材料出处、放大显示的文本行、步骤标记等对基本Markdown格式语法的支持。
            //不包括树型文字表——它是被当作代码块的，逻辑上冲突
            var enBaseMDSyntaxText = App.WorkspaceConfigManager.Get("EnableBaseMDSyntax");
            if (string.IsNullOrEmpty(enBaseMDSyntaxText) == false)
            {
                bool ebms;
                if (bool.TryParse(enBaseMDSyntaxText, out ebms))
                {
                    this.EnableBaseMDSyntax = ebms;
                }
                else this.EnableBaseMDSyntax = true;  // 默认开启
            }
            else
            {
                this.EnableBaseMDSyntax = true;  // 2018年11月6日改为默认开启
            }
            miEnableBaseMDSyntax.IsChecked = this.EnableBaseMDSyntax;

            var createLinksForTitlesText = App.WorkspaceConfigManager.Get("CreateLinksForTitles");
            if (string.IsNullOrEmpty(createLinksForTitlesText) == false)
            {
                bool clft;
                if (bool.TryParse(createLinksForTitlesText, out clft))
                {
                    this.createLinksForTitles = clft;
                }
                else this.CreateLinksForTitles = false;  // 默认关闭吧，大多数人用不着，而且编译速度太慢。
            }
            else
            {
                this.CreateLinksForTitles = false;
            }
            miCreateLinksForTitles.IsChecked = this.CreateLinksForTitles;

            var appendHeadLineText = App.WorkspaceConfigManager.Get("AppendHeadLine");
            if (string.IsNullOrEmpty(appendHeadLineText) == false)
            {
                bool ahl;
                if (bool.TryParse(appendHeadLineText, out ahl))
                {
                    this.appendHeadLine = ahl;
                }
                else this.appendHeadLine = true;
            }
            else this.appendHeadLine = true;
            miAppendHeadLine.IsChecked = this.AppendHeadLine;

            var appendFootLineText = App.WorkspaceConfigManager.Get("AppendFootLine");
            if (string.IsNullOrEmpty(appendFootLineText) == false)
            {
                bool fhl;
                if (bool.TryParse(appendFootLineText, out fhl))
                {
                    this.appendFootLine = fhl;
                }
                else this.appendFootLine = true;
            }
            else this.appendFootLine = true;
            miAppendFootLine.IsChecked = this.AppendFootLine;

            var appendThemeSwitcherText = App.WorkspaceConfigManager.Get("AppendThemeSwitcher");
            if (string.IsNullOrEmpty(appendThemeSwitcherText) == false)
            {
                bool atw;
                if (bool.TryParse(appendThemeSwitcherText, out atw))
                {
                    this.appendThemeSwitcher = atw;
                }
                else this.appendThemeSwitcher = true;
            }
            else this.appendThemeSwitcher = true;
            miAppendThemeSwitcher.IsChecked = this.AppendThemeSwitcher;

            //编译 Html 使用的主题
            var s = App.WorkspaceConfigManager.Get("color");
            if (string.IsNullOrEmpty(s) == false)
            {
                foreach (var i in cmbColor.Items)
                {
                    ComboBoxItem cbi = i as ComboBoxItem;
                    if (cbi == null) continue;

                    if (cbi.Tag.ToString() == s)
                    {
                        cbi.IsSelected = true;
                        break;
                    }
                }
            }
            //cmbColor_SelectionChanged(this, null);

            //是否在 Html 末尾添加编译时间
            var appendTimeOfCompilingText = App.WorkspaceConfigManager.Get("AppendTimeOfCompiling");
            if (string.IsNullOrEmpty(appendTimeOfCompilingText) == false)
            {
                bool at;
                if (bool.TryParse(appendTimeOfCompilingText, out at))
                {
                    this.AppendTimeOfCompiling = at;
                }
                else this.AppendTimeOfCompiling = false;
            }
            else
            {
                this.AppendTimeOfCompiling = false;  // 2019年1月3日默认关闭
            }
            miAppendTimeOfCompiling.IsChecked = this.AppendTimeOfCompiling;

            //是否将图标题编译到顶部（默认底部）
            var imageTitleAtTopText = App.WorkspaceConfigManager.Get("ImageTitleAtTop");
            if (string.IsNullOrEmpty(imageTitleAtTopText) == false)
            {
                bool ttt;
                if (bool.TryParse(imageTitleAtTopText, out ttt))
                {
                    this.ImageTitleAtTop = ttt;
                }
            }
            else
            {
                this.ImageTitleAtTop = false;//默认在底部
            }
            miImageTitleAtTop.IsChecked = this.ImageTitleAtTop;

            //是否将表格标题编译到底部（默认顶部）
            var tableCaptionAtBottomText = App.WorkspaceConfigManager.Get("TableCaptionAtBottom");
            if (string.IsNullOrEmpty(tableCaptionAtBottomText) == false)
            {
                bool tcb;
                if (bool.TryParse(tableCaptionAtBottomText, out tcb))
                {
                    this.TableCaptionAtBottom = tcb;
                }
            }
            else
            {
                this.TableCaptionAtBottom = false;//默认在顶部
            }
            miTableCaptionAtBottom.IsChecked = this.TableCaptionAtBottom;



            //是否格式化编译好的 Html 文本
            var formatAfterCompileText = App.WorkspaceConfigManager.Get("FormatAfterCompile");
            if (string.IsNullOrEmpty(formatAfterCompileText) == false)
            {
                bool fac;
                if (bool.TryParse(formatAfterCompileText, out fac))
                {
                    this.FormatAfterCompile = fac;
                }
            }
            else
            {
                this.FormatAfterCompile = false;//默认关闭
            }
            miFormatAfterCompile.IsChecked = this.FormatAfterCompile;

            //指示编译后的 Html 文件中六级标题是否支持折叠、手工折叠还是自动折叠
            var htmlHeadersCollapse = App.WorkspaceConfigManager.Get("HtmlHeadersCollapse");
            if (string.IsNullOrEmpty(htmlHeadersCollapse) == false)
            {
                HtmlHeadersCollapseType hhct;
                var result = Enum.TryParse<HtmlHeadersCollapseType>(htmlHeadersCollapse, out hhct);
                if (result)
                {
                    this.HtmlHeadersCollapse = hhct;
                }
            }

            switch (this.HtmlHeadersCollapse)
            {
                case HtmlHeadersCollapseType.Auto:
                    {
                        miAutoCollapseHtmlHeaders.IsChecked = true;
                        miManualCollapseHtmlHeaders.IsChecked = false;
                        miNoCollapseHtmlHeaders.IsChecked = false;
                        break;
                    }
                case HtmlHeadersCollapseType.Manual:
                    {
                        miAutoCollapseHtmlHeaders.IsChecked = false;
                        miManualCollapseHtmlHeaders.IsChecked = true;
                        miNoCollapseHtmlHeaders.IsChecked = false;
                        break;
                    }
                //case HtmlHeadersCollapseType.NO:
                default:
                    {
                        miAutoCollapseHtmlHeaders.IsChecked = false;
                        miManualCollapseHtmlHeaders.IsChecked = false;
                        miNoCollapseHtmlHeaders.IsChecked = true;
                        break;
                    }
            }

            //编译 Html 六级标题时是否进行自动编号
            var autoNumberHeaders = App.WorkspaceConfigManager.Get("AutoNumberHeaders");
            if (string.IsNullOrEmpty(autoNumberHeaders) == false)
            {
                bool an;
                if (bool.TryParse(autoNumberHeaders, out an))
                {
                    this.AutoNumberHeaders = an;
                }
            }
            miAutoNumberHeaders.IsChecked = this.AutoNumberHeaders;

            var autoNumberStyleText = App.WorkspaceConfigManager.Get("AutoNumberStyle");
            if (string.IsNullOrEmpty(autoNumberStyle) == false)
            {
                this.autoNumberStyle = autoNumberStyleText;
            }
            else
            {
                this.autoNumberStyle = "en_us";
            }

            switch (this.autoNumberStyle)
            {
                case "zh_cn":
                    {
                        miAutoNumberChineseStyle.IsChecked = true;
                        miAutoNumberEnglishStyle.IsChecked = false;
                        break;
                    }
                default:
                    {
                        miAutoNumberChineseStyle.IsChecked = false;
                        miAutoNumberEnglishStyle.IsChecked = true;
                        break;
                    }
            }

            var cleanHeadersText = App.WorkspaceConfigManager.Get("CleanHeaders");
            if (string.IsNullOrWhiteSpace(cleanHeadersText) == false)
            {
                bool ch;
                if (bool.TryParse(cleanHeadersText, out ch))
                {
                    this.CleanHeaders = ch;
                }
            }
            miCompileCleanHeaderLines.IsChecked = this.CleanHeaders;

            var tryToCollapseCustomRegionText = App.WorkspaceConfigManager.Get("TryToCollapseCustomRegion");
            if (string.IsNullOrWhiteSpace(tryToCollapseCustomRegionText))
            {
                bool ttccr;
                if (bool.TryParse(tryToCollapseCustomRegionText, out ttccr))
                {
                    this.TryToCollapseCustomRegion = ttccr;
                }
            }
            miTryToCollapseCustomRegion.IsChecked = this.TryToCollapseCustomRegion;

            //miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            //this.WorkspaceConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
            //是否将 <code></code>块编译为支持填空的形式
            var compileCodeToFillBlank = App.WorkspaceConfigManager.Get("CompileCodeToFillBlank");
            if (string.IsNullOrEmpty(compileCodeToFillBlank) == false)
            {
                bool cctfb;
                if (bool.TryParse(compileCodeToFillBlank, out cctfb))
                {
                    this.CompileCodeToFillBlank = cctfb;
                }
            }
            miFillblankMode.IsChecked = this.CompileCodeToFillBlank;

            //是否强制为 Html 文件编译出左边栏菜单
            //如果为假，则根据页面内的设置文本决定是否编译左边栏菜单，格式是：
            //    ；[MENU]： 指定要编译左边栏菜单
            var compilePageMenu = App.WorkspaceConfigManager.Get("CompilePageMenu");
            if (string.IsNullOrEmpty(compilePageMenu) == false)
            {
                bool cpm;
                if (bool.TryParse(compilePageMenu, out cpm))
                {
                    this.CompilePageMenu = cpm;
                }
            }
            miCompilePageMenu.IsChecked = this.CompilePageMenu; ;

            //指示编译的 Html 文件中的试题是否应该隐藏答案
            var hideExamAnswer = App.WorkspaceConfigManager.Get("HideExamAnswer");
            if (string.IsNullOrEmpty(hideExamAnswer) == false)
            {
                bool hcqa;
                if (bool.TryParse(hideExamAnswer, out hcqa))
                {
                    this.HideExamAnswer = hcqa;
                }
            }
            miHideExamAnswer.IsChecked = this.HideExamAnswer;

            //指定编译的 Html 文件使用的编码方式
            var encoding = App.WorkspaceConfigManager.Get("Encoding");
            if (string.IsNullOrEmpty(encoding) == false)
            {
                RefreshEncoding(encoding);   //GB2312 或者 GB18030
            }
            else
            {
                RefreshEncoding("utf-8");
            }

            //指定编译的 Html 文件的 IE 兼容模式
            var ieVersion = App.WorkspaceConfigManager.Get("IE_X_UA_Compatible");
            if (string.IsNullOrEmpty(ieVersion) == false)
            {
                RefreshIE_X_UA_Compatible(ieVersion);   // 8-11 或 Edge
            }
            else
            {
                RefreshIE_X_UA_Compatible("8");
            }

            //编译工作区时是否忽略（跳过）加密文件
            var ignoreEncryptedFiles = App.WorkspaceConfigManager.Get("IgnoreEncryptedFiles");
            if (string.IsNullOrWhiteSpace(ignoreEncryptedFiles) == false)
            {
                bool ignoreEncryptedFilesValue;
                if (bool.TryParse(ignoreEncryptedFiles, out ignoreEncryptedFilesValue))
                {
                    this.IgnoreEncryptedFiles = ignoreEncryptedFilesValue;
                }
                else this.IgnoreEncryptedFiles = true;
            }
            else
            {
                this.IgnoreEncryptedFiles = true;
            }
            miIgnoreEncryptedFile.IsChecked = this.IgnoreEncryptedFiles;

            //编译工作区时是否忽略（跳过）被标记为“废弃”的文件
            var ignoreAbortedFiles = App.WorkspaceConfigManager.Get("IgnoreAbortedFiles");
            if (string.IsNullOrWhiteSpace(ignoreAbortedFiles) == false)
            {
                bool ignoreAbortedFilesValue;
                if (bool.TryParse(ignoreAbortedFiles, out ignoreAbortedFilesValue))
                {
                    this.IgnoreAbortedFiles = ignoreAbortedFilesValue;
                }
            }
            miIgnoreAbortedFile.IsChecked = this.IgnoreAbortedFiles;

            tbxIgnoreAutoLinkTitles.Text = ReadIgnoreAutoLinkTitles();
            lbxTitles.Items.Clear();
        }

        private void LoadWorkspaceChmCompileOptions()
        {
            //指定编译的 CHM 默认情况下是否显示导航栏
            var navBarVisible = App.WorkspaceConfigManager.Get("ChmNavBarVisible");
            if (string.IsNullOrEmpty(navBarVisible) == false)
            {
                RefreshChmNavBarVisible(navBarVisible);
            }
            else
            {
                RefreshChmNavBarVisible("True");
            }

            //指定 CHM 默认使用的图标组
            var chmImageTypeAttrText = App.WorkspaceConfigManager.Get("ChmImageType");
            if (string.IsNullOrWhiteSpace(chmImageTypeAttrText) == false)
            {
                chmImageType = chmImageTypeAttrText;
            }
            else chmImageType = "";

            foreach (var smii in miChmImageTypes.Items)
            {
                var mi = smii as MenuItem;
                if (mi.Tag.ToString().ToLower() == chmImageType.ToLower())
                {
                    mi.IsChecked = true;
                }
                else mi.IsChecked = false;
            }

            //指定要在 CHM 工程中引入的外部文件的列表
            var cfis = App.WorkspaceConfigManager.Get("ChmInportedFiles");
            if (string.IsNullOrWhiteSpace(cfis) == false)
            {
                chmImportDirectoryPathes = cfis;
            }
            else chmImportDirectoryPathes = "";
        }

        private void RememberWorkspaceHtmlCompileOptions()
        {
            //编译 Html 使用的主题
            if (cmbColor.SelectedItem != null)
            {
                var cbi = cmbColor.SelectedItem as ComboBoxItem;
                App.WorkspaceConfigManager.Set("color", cbi.Tag.ToString());
            }
            else
            {
                App.WorkspaceConfigManager.Set("color", "light");
            }

            //是否格式化编译好的 Html 文本
            App.WorkspaceConfigManager.Set("FormatAfterCompile", this.FormatAfterCompile.ToString());

            //指示编译后的 Html 文件中六级标题是否支持折叠、手工折叠还是自动折叠
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());

            //编译 Html 六级标题时是否进行自动编号
            App.WorkspaceConfigManager.Set("AutoNumberHeaders", this.AutoNumberHeaders.ToString());

            //六级标题的样式
            App.WorkspaceConfigManager.Set("AutoNumberStyle", this.AutoNunberStyle);

            //miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            //this.WorkspaceConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
            //是否将 <code></code>块编译为支持填空的形式
            App.WorkspaceConfigManager.Set("CompileCodeToFillBlank", CompileCodeToFillBlank.ToString());

            //是否强制为 Html 文件编译出左边栏菜单
            //如果为假，则根据页面内的设置文本决定是否编译左边栏菜单，格式是：
            //    ；[MENU]： 指定要编译左边栏菜单
            App.WorkspaceConfigManager.Set("CompilePageMenu", CompilePageMenu.ToString());

            //指示编译的 Html 文件中的试题是否应该隐藏答案
            App.WorkspaceConfigManager.Set("HideExamAnswer", HideExamAnswer.ToString());

            //指定编译的 Html 文件使用的编码方式
            App.WorkspaceConfigManager.Set("Encoding", defaultEncoding);

            //指定编译的 Html 文件的 X-UA-Compatible 兼容模式
            App.WorkspaceConfigManager.Set("IE_X_UA_Compatible", defaultIeVersion);

            //编译工作区时是否忽略（跳过）加密文件
            App.WorkspaceConfigManager.Set("IgnoreEncryptedFiles", IgnoreEncryptedFiles.ToString());
        }

        public event EventHandler<WorkspaceChangedEventArgs> WorkspaceChanging;

        protected void OnWorkspaceChanging(object sender, WorkspaceChangedEventArgs e)
        {
            if (WorkspaceChanging != null)
            {
                WorkspaceChanging(sender, e);
            }
        }

        public event EventHandler<WorkspaceChangedEventArgs> WorkspaceChanged;

        protected void OnWorkspaceChanged(object sender, WorkspaceChangedEventArgs e)
        {
            if (WorkspaceChanged != null)
            {
                WorkspaceChanged(sender, e);
            }
        }

        /// <summary>
        /// 更改当前工作区目录。
        /// </summary>
        /// <param name="newWorkspacePath">新工作区目录的路径。</param>
        public void ChangeWorkspace(string newWorkspacePath = null)
        {
            var eventArgs = new WorkspaceChangedEventArgs()
            {
                OldWorkspaceDirectoryFullPath = Globals.PathOfWorkspace,
                NewWorkspaceDirectoryFullPath = newWorkspacePath,
            };

            OnWorkspaceChanging(this, eventArgs);
            if (openAndModifiedWorkspacePythonScripts > 0)
            {
                LMessageBox.Show($"当前工作区有 {openAndModifiedWorkspacePythonScripts} 个 Python 脚本正在编辑且已被修改，无法切换工作区。\r\n\r\n" +
                    $"　　请先考虑是否要保存这些修改过的脚本，然后关闭所有对应的编辑器。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (lightLessonCssEditor != null && lightLessonCssEditor.Visibility == Visibility.Visible)
            {
                lightLessonCssEditor.Close();
                if (lightLessonCssEditor.IsClosed == false) return;
            }

            if (darkLessonCssEditor != null && darkLessonCssEditor.Visibility == Visibility.Visible)
            {
                darkLessonCssEditor.Close();
                if (darkLessonCssEditor.IsClosed == false) return;
            }

            if (lightMenuCssEditor != null && lightMenuCssEditor.Visibility == Visibility.Visible)
            {
                lightMenuCssEditor.Close();
                if (lightMenuCssEditor.IsClosed == false) return;
            }

            if (darkMenuCssEditor != null && darkMenuCssEditor.Visibility == Visibility.Visible)
            {
                darkMenuCssEditor.Close();
                if (darkMenuCssEditor.IsClosed == false) return;
            }

            if (lightPresentationCssEditor != null && lightPresentationCssEditor.Visibility == Visibility.Visible)
            {
                lightPresentationCssEditor.Close();
                if (lightPresentationCssEditor.IsClosed == false) return;
            }

            if (darkPresentationCssEditor != null && darkPresentationCssEditor.Visibility == Visibility.Visible)
            {
                darkPresentationCssEditor.Close();
                if (darkPresentationCssEditor.IsClosed == false) return;
            }

            // 更改工作区之前，要去 Anchors~.txt 中重复的条目。
            AnchorsManager.Save();

            string destWorkspaceFolder = newWorkspacePath;

            if (destWorkspaceFolder == null)
            {
                //选择新工作目录
                //System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog()
                //{
                //    Description = "　　请选择一个目录作为新的工作区目录（要求路径中不能含半角英文空格）。\r\n　　一些必要的css、js和图像文件会被复制到指定的目录中。",
                //    ShowNewFolderButton = true,
                //};
                //System.Windows.Interop.HwndSource source = PresentationSource.FromVisual(this) as System.Windows.Interop.HwndSource;

                //System.Windows.Forms.IWin32Window win = new WinFormWindow(source.Handle);
                //System.Windows.Forms.DialogResult result = fbd.ShowDialog(win);

                //if (result.Equals(System.Windows.Forms.DialogResult.OK) && Directory.Exists(fbd.SelectedPath))
                //{
                //    if (fbd.SelectedPath.Contains(" "))
                //    {
                //        var result2 = LMessageBox.Show("您选择的新目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　您确定要这样做吗？",
                //        Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                //        // NPA_ means No Prompt Again.

                //        if (result2 != MessageBoxResult.Yes) return;
                //    }

                //    destWorkspaceFolder = fbd.SelectedPath;
                //}
                //else return;//相当于用户取消了操作。

                var dialog = new CommonOpenFileDialog() { Multiselect = false, };
                dialog.IsFolderPicker = true;//设置为选择文件夹
                dialog.Title = Globals.AppName + " - " + "另选工作区目录";
                if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;  // 用户取消操作

                if (Directory.Exists(dialog.FileName))
                {
                    // 为什么这里不禁止使用非空目录？这是因为用户可能通过此途径切换工作区！

                    if (dialog.FileName.Contains(" "))
                    {
                        var result2 = LMessageBox.Show("您选择的新目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　您确定要这样做吗？",
                        Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                        // NPA_ means No Prompt Again.

                        if (result2 != MessageBoxResult.Yes) return;
                    }

                    destWorkspaceFolder = dialog.FileName;
                }
                else return;//相当于用户取消了操作。
            }
            else
            {
                if (newWorkspacePath.Length > 0)
                {
                    if (newWorkspacePath[newWorkspacePath.Length - 1] != Path.DirectorySeparatorChar)
                        newWorkspacePath += Path.DirectorySeparatorChar;
                }

                if (Directory.Exists(destWorkspaceFolder) == false)
                {
                    var result = LMessageBox.Show("物理磁盘上不存在该目录，要创建一个新目录吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                    if (result != MessageBoxResult.Yes) return;

                    if (destWorkspaceFolder.Contains(" "))
                    {
                        var result2 = LMessageBox.Show("目标目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　你确定要这样做吗？",
                                            Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                        if (result2 != MessageBoxResult.Yes) return;
                    }

                    System.IO.Directory.CreateDirectory(destWorkspaceFolder);
                }
            }

            var destEditingFilePath = destWorkspaceFolder + "\\~.editing";
            if (File.Exists(destEditingFilePath))
            {
                var result = LMessageBox.Show(
                    "　　此工作区貌似已经处于打开状态或者上次编辑此工作区时程序曾经意外崩溃。\r\n\r\n" +
                    "　　重复打开工作区易造成意外错误，真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "", null, null);
                if (result != MessageBoxResult.Yes)
                    return;
            }

            //[废弃]记录工作区管理器中各项的“展开/折叠”状态。已由WorkspaceItems.xml文件接管。
            //SaveWorkspaceCollapseStatus();

            //检查文件保存状态
            try
            {
                List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti != null && eti.IsModified)
                    {
                        needSavingDocumentList.Add(eti);
                    }
                }

                if (needSavingDocumentList.Count > 0)
                {
                    MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                        Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
                    switch (r)
                    {
                        case MessageBoxResult.Yes:
                            {
                                string saveInfo;
                                foreach (MarkdownEditor eti in needSavingDocumentList)
                                {
                                    saveInfo = eti.SaveDocument();
                                    if (saveInfo == "用户取消操作")
                                    {
                                        return;//不更改工作目录
                                    }
                                    else if (saveInfo != string.Empty)
                                    {
                                        LMessageBox.Show(saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                        return;//不更改工作目录
                                    }
                                    else { AnchorsManager.Load(); }
                                }

                                this.mainTabControl.Items.Clear();
                                break;
                            }
                        case MessageBoxResult.Cancel: return;//不更改工作目录。
                        default://No,放弃保存，更改工作目录。
                            {
                                break;
                            }
                    }
                }

                //记录下当前打开的文档列表。
                RememberOpenedFilesForWorkspace();

                //记录当前工作区 Html 编译选项。
                RememberWorkspaceHtmlCompileOptions();

                this.mainTabControl.Items.Clear();

                //用于切换工作区。
                var oldEditingFilePath = Globals.PathOfWorkspace + "\\~.editing";
                if (File.Exists(oldEditingFilePath))
                {
                    File.Delete(oldEditingFilePath);
                }

                if (File.Exists(oldEditingFilePath))
                {
                    File.Delete(oldEditingFilePath);
                }

                App.WorkspaceConfigManager = null;//重置工作区配置文件管理器。

                //tvWorkDirectory.IsEnabled = false;

                //下面才载入新工作区

                //用于判断当前工作区目录是否已被打开，以便提示用户不要重复打开工作区
                File.WriteAllText(destEditingFilePath, "This workspace is in editing.");

                App.ConfigManager.Set("Workspace", destWorkspaceFolder);
                Globals.PathOfWorkspace = destWorkspaceFolder;
                this.tbxPathOfWorkspace.Text = destWorkspaceFolder;

                CopyCssAndResourceFiles(destWorkspaceFolder);

                //载入工作区 Chm 编译选项。
                LoadWorkspaceChmCompileOptions();

                if (this.WorkspaceManager.LoadWorkspaceFromXml(null) == false)
                {
                    this.WorkspaceManager.RefreshWorkspaceTreeView(null);       //已限制
                }

                previewFrame.Source = null;

                //记载到一个列表文件中
                // var lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                WriteHistoryWorkspacesFile(destWorkspaceFolder);

                for (int i = lbxHistoryWorkspaces.Items.Count - 1; i >= 0; i--)
                {
                    var rwitem = lbxHistoryWorkspaces.Items[i] as RecentDirectoryListBoxItem;
                    if (rwitem != null && rwitem.DirectoryPath == destWorkspaceFolder)
                    {
                        lbxHistoryWorkspaces.Items.Remove(rwitem);
                    }
                }

                var rw = new RecentDirectoryListBoxItem(destWorkspaceFolder)
                {
                    ToolTip = "双击设置为当前工作区目录",
                };
                rw.MouseDoubleClick += rw_MouseDoubleClick;

                lbxHistoryWorkspaces.Items.Insert(0, rw);
                lbxHistoryWorkspaces.SelectedIndex = 0;

                tcManagerPanels.SelectedItem = tiWorkspace;

                RefreshWorkspaceHistoryList();

                LoadRememberOpenedFiles();//载入上次打开的文件。

                //[废弃]WorkspaceItems.xml接管了记录工作区管理器中各条目折叠状态的记录。
                //LoadWorkspaceCollapseStatus();

                //载入工作区 Html 编译选项。
                LoadWorkspaceHtmlCompileOptions();

                //tvWorkDirectory.IsEnabled = true;

                //如果有必要，刷新对照区。
                if (this.PerspectiveMode == Perspective.CompareMode)
                {
                    var activeEditor = ActivedEditor;
                    if (activeEditor != null)
                    {
                        activeEditor.SendToRightCompareArea();
                    }
                }

                AddJumpTask(destWorkspaceFolder);

                //触发WorkspaceChanged事件
                eventArgs.NewWorkspaceDirectoryFullPath = Globals.PathOfWorkspace;
                OnWorkspaceChanged(this, eventArgs);

                //判断是否新建的工作区
                if (File.Exists(WorkspaceManager.WorkspaceTreeviewItemsXmlPath) == false)
                {
                    WorkspaceManager.SaveWorkspaceTreeviewToXml();
                }

                TryCreatoDefaultMetaFileForNewWorkspaceDirectory();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        /// <summary>
        /// 将当前工作区的目录结构复制到另一个空目录中，并建立一系列空文件。
        /// 这样，这个空目录就成为一个全新的、结构近乎完全相同的新的空工作区了——它与原工作区的根目录元文件标题、名称可能不同。
        /// </summary>
        public void CopyStructAndCreateNewWorkspace()
        {
            try
            {
                if (tvWorkDirectory.Items.Count <= 0)
                {
                    LMessageBox.ShowWarning("当前工作区中无条目，不能生成新工作区！", this);
                    return;
                }
                var destWorkspaceFolder = "";
                var dialog = new CommonOpenFileDialog() { Multiselect = false, };
                dialog.IsFolderPicker = true;//设置为选择文件夹
                dialog.AllowNonFileSystemItems = false;
                dialog.EnsurePathExists = true;
                dialog.EnsureReadOnly = false;
                dialog.EnsureValidNames = true;
                dialog.Multiselect = false;
                dialog.Title = Globals.AppName + " - " + "请选择空目录：";
                if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;  // 用户取消操作

                var destDi = new DirectoryInfo(dialog.FileName);
                if (destDi.GetFileSystemInfos().Length > 0)
                {
                    LMessageBox.ShowWarning("指定目录非空。为避免误覆盖文件，禁止使用此目录。");
                    return;
                }

                if (Directory.Exists(dialog.FileName))
                {
                    if (dialog.FileName.Contains(" "))
                    {
                        var result2 = LMessageBox.Show("您选择的新目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　您确定要这样做吗？",
                        Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                        // NPA_ means No Prompt Again.

                        if (result2 != MessageBoxResult.Yes) return;
                    }

                    destWorkspaceFolder = dialog.FileName;
                }
                else return;//相当于用户取消了操作。

                if (destWorkspaceFolder.EndsWith("\\") == false)
                    destWorkspaceFolder += "\\";
                var destWorkspaceFolderInfo = new DirectoryInfo(destWorkspaceFolder);
                // 不能用：CreateDirectoryMetaMdFile(); ——这个方法是针对当前工作区的
                var directoryMdFileFullName = destWorkspaceFolder + "_" + destWorkspaceFolderInfo.Name + ".md";
                var workspaceTitle = (Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem).Title;
                CreateMetaFileForOtherNewWorkspace(destWorkspaceFolderInfo, destWorkspaceFolder, directoryMdFileFullName, workspaceTitle);
                foreach (var item in (Globals.MainWindow.tvWorkDirectory.Items[0] as TreeViewItem).Items)
                {
                    var wtvi = item as WorkspaceTreeViewItem;
                    if (wtvi == null) continue;
                    CreateEntryForAnotherNewWorkspace(wtvi, destWorkspaceFolder);
                }

                XmlDocument workspaceItemsXml = new XmlDocument();
                workspaceItemsXml.Load(WorkspaceManager.WorkspaceTreeviewItemsXmlPath);
                var rootNode = workspaceItemsXml.SelectSingleNode("Item") as XmlNode;
                rootNode.SetAttribute("Title", workspaceTitle);

                RemoveExtraNodesInWorkspaceItemsXmlDocument(rootNode, "ItemType", new string[] { "Folder", "File", });

                workspaceItemsXml.Save(destWorkspaceFolder + "WorkspaceItems.xml");
                var answer = LMessageBox.Show("已根据当前工作区目录结构生成新工作区到：\r\n\r\n　　" + destWorkspaceFolder + "\r\n\r\n" +
                   "　　要打开新工作区吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (answer != MessageBoxResult.Yes)
                {
                    try
                    {
                        WorkspaceFolderIcon.SetWorkspaceFolderIcon(destWorkspaceFolder);
                    }
                    catch (Exception ex)
                    {
                        LMessageBox.ShowWarning("未能顺利设置新工作区文件夹的图标。错误消息如下：\r\n\r\n　　" + ex.Message);
                    }
                    return;
                }

                if (destWorkspaceFolder.Contains(" "))
                {
                    System.Diagnostics.Process.Start(Globals.FullPathOfApp, "\"" + destWorkspaceFolder + "\"");
                }
                else
                {
                    System.Diagnostics.Process.Start(Globals.FullPathOfApp, destWorkspaceFolder);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        private void RemoveExtraNodesInWorkspaceItemsXmlDocument(XmlNode node, string retainAttributeName, string[] retainAttributeValues, bool ignoreCase = true)
        {
            if (node.ParentNode == null) return;
            node.SetAttribute("StatuHeader", "");
            node.SetAttribute("StatuTail", "");
            foreach (var child in node.ChildNodes)
            {
                var childNode = child as XmlNode;
                if (childNode == null) continue;
                RemoveExtraNodesInWorkspaceItemsXmlDocument(childNode, retainAttributeName, retainAttributeValues);
            }

            var attrValue = node.GetAttributeValueText(retainAttributeName);
            bool isRetain = false;
            foreach (var rabv in retainAttributeValues)
            {
                if (ignoreCase)
                {
                    if (rabv.ToLower() == attrValue.ToLower())
                    {
                        isRetain = true;
                        break;
                    }
                }
                else
                {
                    if (rabv == attrValue)
                    {
                        isRetain = true;
                        break;
                    }
                }
            }

            if (isRetain == false)
            {
                node.ParentNode.RemoveChild(node);
            }
        }

        /// <summary>
        /// 用于根据当前工作区的结构生成另一个空工作区。
        /// </summary>
        /// <param name="item">当前工作区中某个条目。</param>
        private void CreateEntryForAnotherNewWorkspace(WorkspaceTreeViewItem item, string newWorkspaceFullPath)
        {
            try
            {
                switch (item.ItemType)
                {
                    case WorkspaceTreeViewItem.Type.Folder:
                        {
                            var destMdFileFullPathInfo = new FileInfo(newWorkspaceFullPath + item.MetaFilePath.Substring(Globals.PathOfWorkspace.Length));
                            CreateMetaFileForOtherNewWorkspace(new DirectoryInfo(destMdFileFullPathInfo.Directory.FullName), newWorkspaceFullPath, destMdFileFullPathInfo.FullName, item.Title);
                            foreach (var subItem in item.Items)
                            {
                                var subWtvi = subItem as WorkspaceTreeViewItem;
                                if (subWtvi == null) continue;
                                CreateEntryForAnotherNewWorkspace(subWtvi, newWorkspaceFullPath);
                            }
                            break;
                        }
                    case WorkspaceTreeViewItem.Type.File:
                        {
                            var destMdFileFullPathInfo = new FileInfo(newWorkspaceFullPath + item.FullPath.Substring(Globals.PathOfWorkspace.Length));

                            var commonTemplateText = "";
                            if (File.Exists(Globals.PathOfWorkspaceCommonTemplateFile))
                            {
                                using (StreamReader sr = File.OpenText(Globals.PathOfWorkspaceCommonTemplateFile))
                                {
                                    commonTemplateText = sr.ReadToEnd();
                                }
                            }

                            using (StreamWriter sw = File.CreateText(destMdFileFullPathInfo.FullName))
                            {
                                var docTitle = item.Title;
                                sw.Write($"\r\n%{docTitle}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n{commonTemplateText}");
                            }
                            break;
                        }
                }
            }
            catch
            {
                return;
            }
        }

        /// <summary>
        /// 专用于根据当前工作区的目录结构生成新的工作区。
        /// </summary>
        /// <param name="destFolderInfo"></param>
        /// <param name="destWorkspacePath"></param>
        /// <param name="destMdFileFullPath"></param>
        private void CreateMetaFileForOtherNewWorkspace(DirectoryInfo destFolderInfo,
            string destWorkspacePath, string destMdFileFullPath, string defaultTitle = null)
        {
            if (File.Exists(destMdFileFullPath) == false)
            {
                if (destFolderInfo.Exists == false)
                {
                    destFolderInfo.Create();
                }

                using (StreamWriter sw = File.CreateText(destMdFileFullPath))
                {
                    var isWorkspaceMetaFile = false;
                    var a = destFolderInfo.FullName.ToLower(); if (a.EndsWith("\\") == false) a += "\\";
                    var b = destWorkspacePath.ToLower(); if (b.EndsWith("\\") == false) b += "\\";
                    if (a == b) isWorkspaceMetaFile = true;
                    var metaInlineEnviromentSpan = isWorkspaceMetaFile ? Properties.Resources.workspaceMetaFileInlineEnviromentTextSpan : Properties.Resources.metaFileInlineEnviromentTextSpan;
                    sw.Write($"\r\n%{FormatDocumentTitle(string.IsNullOrWhiteSpace(defaultTitle) ? destFolderInfo.Name : defaultTitle)}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n{metaInlineEnviromentSpan}\r\n\r\n");
                }
            }
        }

        /// <summary>
        /// 如果有必要，自动为工作区目录创建目录元文件，免去未创建根目录元文件时编译 CHM 工程时出错提示。
        /// </summary>
        private void TryCreatoDefaultMetaFileForNewWorkspaceDirectory()
        {
            if (tvWorkDirectory.Items.Count > 0)
            {
                var workspaceItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
                if (workspaceItem != null)
                {
                    var workspaceMetaFilePath = workspaceItem.MetaFilePath;
                    if (workspaceMetaFilePath.ToLower().EndsWith(".md") && File.Exists(workspaceMetaFilePath) == false)
                    {
                        CreateDirectoryMetaMdFile(new DirectoryInfo(workspaceItem.FullPath), workspaceMetaFilePath);
                    }
                }
            }
        }

        private void LoadRememberedPerspectiveMode()
        {
            var perspectiveModeText = App.ConfigManager.Get("PerspectiveMode");
            var perspectiveIndex = (int)Perspective.Normal;
            Perspective perspectiveMode = Perspective.Default;
            if (string.IsNullOrEmpty(perspectiveModeText) == false)
            {
                if (Enum.TryParse<Perspective>(perspectiveModeText, out perspectiveMode))
                {
                    perspectiveIndex = (int)perspectiveMode;
                }
            }
            cmbPerspective.SelectedIndex = perspectiveIndex;
            RefreshPersective(perspectiveMode);
        }

        /// <summary>
        /// 刷新历史工作区列表。
        /// </summary>
        private void RefreshWorkspaceHistoryList()
        {
            for (int i = 0; i < lbxHistoryWorkspaces.Items.Count; i++)
            {
                var item = lbxHistoryWorkspaces.Items[i] as RecentDirectoryListBoxItem;
                if (item == null) continue;

                item.IndexText = (i + 1).ToString();
            }
        }

        /// <summary>
        /// 载入上次关闭程序时打开的那些文件。需要开启“记住打开的文件”这个开关才应起作用。
        /// </summary>
        private void LoadRememberOpenedFiles()
        {
            if (this.RememberOpenedFiles == false) return;

            var openedFilesText = App.WorkspaceConfigManager.Get("OpenedFiles");
            var rememberedActiveDocumentFullPath = App.WorkspaceConfigManager.Get("ActiveDocumentFullPath");
            //文档初始化

            if (string.IsNullOrEmpty(openedFilesText) || this.RememberOpenedFiles == false) return;

            var openedFiles = openedFilesText.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            if (openedFiles.Length <= 0) return;

            FileSelectorDialog fsd = new FileSelectorDialog(openedFiles)
            {
                Owner = this,
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
            };
            if (fsd.ShowDialog() != true) return;

            if (fsd.SelectedFilePaths != null && fsd.SelectedFilePaths.Count > 0)
            {
                OpenDocuments(fsd.SelectedFilePaths);
            }

            bool shouldFocusFstFile = true;

            var activeDocumentFullPath = App.WorkspaceConfigManager.Get("ActiveDocumentFullPath");
            if (File.Exists(activeDocumentFullPath))
            {
                foreach (var item in this.mainTabControl.Items)
                {
                    var me = item as MarkdownEditor;
                    if (me == null || me.FullFilePath == null) continue;

                    if (me.FullFilePath.ToLower() == activeDocumentFullPath.ToLower())
                    {
                        this.mainTabControl.SelectedItem = me;
                        shouldFocusFstFile = false;
                        break;
                    }
                }
            }

            if (shouldFocusFstFile && this.mainTabControl.Items.Count > 0)
            {
                this.mainTabControl.SelectedIndex = 0;
            }

            if (string.IsNullOrWhiteSpace(rememberedActiveDocumentFullPath) == false &&
                File.Exists(rememberedActiveDocumentFullPath))
            {
                int index = -1;
                for (int i = 0; i < this.mainTabControl.Items.Count; i++)
                {
                    var editor = this.mainTabControl.Items[i] as MarkdownEditor;
                    if (editor == null) continue;

                    if (editor.FullFilePath == rememberedActiveDocumentFullPath)
                    {
                        index = i;
                        break;
                    }
                }

                if (index >= 0)
                {
                    this.mainTabControl.SelectedIndex = index;
                }
            }
        }

        /// <summary>
        /// 重写“历史工作区列表”到文件中。会将新选中的工作区移动到历史工作区列表的头部。
        /// </summary>
        /// <param name="destWorkspaceFolder">新工作区目录的路径。</param>
        private static void WriteHistoryWorkspacesFile(string destWorkspaceFolder)
        {
            if (destWorkspaceFolder.EndsWith("\\"))
            {
                destWorkspaceFolder = destWorkspaceFolder.Substring(0, destWorkspaceFolder.Length - 1);
            }

            //destWorkspaceFolder = destWorkspaceFolder.ToLower();
            //上面这行会造成不必要的小写化。
            var lowerDestWorkspaceFolder = destWorkspaceFolder.ToLower();

            if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
            {
                string[] lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName, new UnicodeEncoding());
                var newLinesList = new List<string>();
                foreach (var line in lines)
                {
                    string tmp;
                    if (line.EndsWith("\\"))
                    {
                        tmp = line.Substring(0, line.Length - 1).ToLower();
                    }
                    else tmp = line.ToLower();

                    if (tmp == lowerDestWorkspaceFolder) continue;

                    newLinesList.Add(line);  // 不要写入小写版本！
                }

                var newLines = new string[newLinesList.Count + 1];
                newLines[0] = destWorkspaceFolder;
                for (int i = 1; i < newLines.Length; i++)
                {
                    newLines[i] = newLinesList[i - 1];
                }

                File.WriteAllLines(Globals.PathOfHistoryWorkspaceFileFullName, newLines, new UnicodeEncoding());
            }
            else
            {
                File.WriteAllLines(Globals.PathOfHistoryWorkspaceFileFullName, new string[] { destWorkspaceFolder }, new UnicodeEncoding());
            }
        }

        /// <summary>
        /// 更改所有编辑器默认字体。
        /// </summary>
        private void fontFamilyList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (fontFamilyList.SelectedItem == null) return;
            var cbi = fontFamilyList.SelectedItem as ComboBoxItem;
            if (cbi == null) return;
            var item = cbi.Content as FontFamilyListItem;
            if (item == null) return;

            //右工具栏中的对照区不能直接对右工具栏变化字号
            foreach (var ue in this.tcRightToolBar.Items)
            {
                MarkdownEditor eti = ue as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.FontFamily = item.FontFamily;
            }

            //左工具栏中的对照区不能直接对左工具栏变化字号
            foreach (var ue in this.tcManagerPanels.Items)
            {
                MarkdownEditor eti = ue as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.FontFamily = item.FontFamily;
            }

            this.mainTabControl.FontFamily = item.FontFamily;
            this.tvOutLine.FontFamily = item.FontFamily;

            string fontEnName;
            if (item.FontFamily.FamilyNames.TryGetValue(System.Windows.Markup.XmlLanguage.GetLanguage("en-us"), out fontEnName))
            {
                App.ConfigManager.Set("FontFamily", fontEnName);
            }
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void miHelp_Click(object sender, RoutedEventArgs e)
        {
            ShowHelp();
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void ShowHelp()
        {
            tcRightToolBar.SelectedItem = tiHelp;
            if (tcRightToolBar.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 显示/隐藏左工具栏。
        /// </summary>
        private void bdLeftToolsController_Click(object sender, RoutedEventArgs e)
        {
            SwitchLeftToolBarToggle();
        }

        /// <summary>
        /// 显示/隐藏左工具栏。
        /// </summary>
        public void SwitchLeftToolBarToggle()
        {
            if (cmbPerspective.SelectedIndex >= 8)
            {
                LMessageBox.ShowWarning("【迷你模式】和【纵向模式】这两种透视图横向空间不够！");
                return;
            }

            if (cdLeftToolsArea.ActualWidth > 100)
            {
                cdLeftToolsArea.Width = new GridLength(0);
            }
            else
            {
                cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
            }
        }

        /// <summary>
        /// 显示/隐藏右工具栏。
        /// </summary>
        internal void SwitchRightToolBarToggle()
        {
            if (cmbPerspective.SelectedIndex >= 8)
            {
                LMessageBox.ShowWarning("【迷你模式】和【纵向模式】这两种透视图横向空间不够！");
                return;
            }

            if (cdRightToolsArea.ActualWidth > 100)
            {
                cdRightToolsArea.Width = new GridLength(0);
            }
            else
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 显示/隐藏“查找/替换”面板。
        /// </summary>
        internal void SwitchFindAndReplacePanelToggle()
        {
            if (rdFindAndReplace.ActualHeight > 0)
            {
                rdFindAndReplace.Height = new GridLength(0);
            }
            else
            {
                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                cmbFindText.UpdateLayout();
            }
        }

        /// <summary>
        /// 显示、隐藏右工具栏。
        /// </summary>
        private void bdRightToolsController_Click(object sender, RoutedEventArgs e)
        {
            SwitchRightToolBarToggle();
        }

        /// <summary>
        /// 切换“保存前自动格式化Markdown文本”的开关。
        /// </summary>
        private void miFormatBeforeSave_Click(object sender, RoutedEventArgs e)
        {
            this.miFormatBeforeSave.IsChecked = !this.miFormatBeforeSave.IsChecked;
            App.ConfigManager.Set("AutoFormat", miFormatBeforeSave.IsChecked.ToString());
        }

        private bool rememberOpenedFiles = false;
        /// <summary>
        /// 决定要不要下次启动时自动打开上次关闭时打开的那些文件。
        /// </summary>
        public bool RememberOpenedFiles
        {
            get { return rememberOpenedFiles; }
            set { rememberOpenedFiles = value; }
        }

        private bool alwaysCompileBeforePreview = false;
        /// <summary>
        /// 让用户决定是否每次预览前都强制编译。
        /// 默认关闭此选项，根据找到的对应的 Html 文件的时间来判断是否比当前 Markdown 更新。
        /// </summary>
        public bool AlwaysCompileBeforePreview
        {
            get { return alwaysCompileBeforePreview; }
            set { alwaysCompileBeforePreview = value; }
        }

        /// <summary>
        /// 切换“记住打开的文档”属性的值。如果为真，下次启动时会自动打开上次关闭时打开的那些文件。
        /// </summary>
        private void miRememberOpenedFiles_Click(object sender, RoutedEventArgs e)
        {
            miRememberOpenedFiles.IsChecked = !miRememberOpenedFiles.IsChecked;
            this.RememberOpenedFiles = miRememberOpenedFiles.IsChecked;
            App.ConfigManager.Set("RememberOpenedFiles", miRememberOpenedFiles.IsChecked.ToString());
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void btnHelp_Click(object sender, RoutedEventArgs e)
        {
            ShowHelp();
        }

        private bool selectCellFirst = false;
        /// <summary>
        /// 编辑时按Tab键，决定是否选选中当前文字表单元格中所有文本。
        /// </summary>
        public bool SelectCellFirst
        {
            get { return selectCellFirst; }
            set { selectCellFirst = value; }
        }

        public enum HtmlHeadersCollapseType { Auto, Manual, No }

        private HtmlHeadersCollapseType htmlHeadersCollapse = MainWindow.HtmlHeadersCollapseType.Manual;
        /// <summary>
        /// 决定编译后的 Html 文档中的各标题是否自动折叠。
        /// </summary>
        public HtmlHeadersCollapseType HtmlHeadersCollapse
        {
            get { return htmlHeadersCollapse; }
            set { htmlHeadersCollapse = value; }
        }

        private bool autoNumberHeaders = true;
        /// <summary>
        /// 决定是否让编译后的 Html 文档中的六级标题都支持自动编号。
        /// </summary>
        public bool AutoNumberHeaders
        {
            get { return autoNumberHeaders; }
            set { autoNumberHeaders = value; }
        }

        private string autoNumberStyle = "en_us";
        /// <summary>
        /// 自动编号的样式。目前"zh_cn"类似中文公文的编号样式（五级）。
        /// 默认是英文样式（简单地以句点分隔层级）。
        /// </summary>
        public string AutoNunberStyle
        {
            get { return this.autoNumberStyle; }
            set { this.autoNumberStyle = value; }
        }

        private bool cleanHeaders = false;
        /// <summary>
        /// 指定编译后的六级标题是否不带格式。如果为真，编译后的标题只会比平常字号稍大些。
        /// </summary>
        public bool CleanHeaders
        {
            get { return cleanHeaders; }
            set { cleanHeaders = value; }
        }

        private bool tryToCollapseCustomRegion = false;
        /// <summary>
        /// 尝试折叠自定义折叠区。注：要有首标题或尾标题才行。
        /// </summary>
        public bool TryToCollapseCustomRegion
        {
            get { return tryToCollapseCustomRegion; }
            set { tryToCollapseCustomRegion = value; }
        }

        private bool compileCodeToFillBlank = false;
        /// <summary>
        /// 决定是否将编译后的 Html 文档中的行内代码块初始色彩设置为透明色——这样就呈现出填空题的效果。
        /// </summary>
        public bool CompileCodeToFillBlank
        {
            get { return compileCodeToFillBlank; }
            set { compileCodeToFillBlank = value; }
        }

        private bool compilePageMenu = false;
        /// <summary>
        /// 决定是否为 Html 网页强制编译左边栏菜单——否则须文档中带 ；[Menu]： 标记行才可以。
        /// </summary>
        public bool CompilePageMenu
        {
            get { return compilePageMenu; }
            set { compilePageMenu = value; }
        }

        private bool hideExamAnswer = true;
        /// <summary>
        /// 决定编译后的 Html 文档中的各试题是否隐藏答案。
        /// </summary>
        public bool HideExamAnswer
        {
            get { return hideExamAnswer; }
            set { hideExamAnswer = value; }
        }

        private bool textAutoWrap = true;
        /// <summary>
        /// 决定编辑器中的文本是否自动折行的开关。
        /// </summary>
        public bool TextAutoWrap
        {
            get { return this.textAutoWrap; }
            set
            {
                this.textAutoWrap = value;
                RefreshTextAutoWrap();
            }
        }

        /// <summary>
        /// 刷新各编辑器文本自动折行状态。
        /// </summary>
        private void RefreshTextAutoWrap()
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var editor = item as MarkdownEditor;
                if (editor == null) continue;

                editor.EditorBase.WordWrap = this.textAutoWrap;
            }
        }

        /// <summary>
        /// 将当前编辑器的文本折行状态刷新到状态条上。
        /// </summary>
        public void RefreshTextAutoWrapToStatusBar()
        {
            if (mainTabControl.HasItems == false)
            {
                bdAutoWrap.Visibility = Visibility.Hidden;
                return;
            }

            var selItem = mainTabControl.SelectedItem as MarkdownEditor;
            if (selItem == null)
            {
                bdAutoWrap.Visibility = Visibility.Hidden;
                return;
            }

            bdAutoWrap.Visibility = Visibility.Visible;
            if (selItem.EditorBase.WordWrap)
            {
                bdAutoWrap.Background = Brushes.White;
            }
            else bdAutoWrap.Background = Brushes.Transparent;
        }

        private bool enableBaseMDSyntax = true;  // 2018年11月6日改为：默认开启
        /// <summary>
        /// 是否启用任务列表项、二维文字表单元格等元素中对基本Markdown格式语法的支持。默认关闭。
        /// </summary>
        public bool EnableBaseMDSyntax
        {
            get { return this.enableBaseMDSyntax; }
            set { this.enableBaseMDSyntax = value; }
        }

        private bool createLinksForTitles = false;  // 2018年12月24日添加，2019年1月3日默认关闭（会拖慢编译速度）
        /// <summary>
        /// 是否启用“在正文中自动创建对某文章标题的链接”功能。
        /// </summary>
        public bool CreateLinksForTitles
        {
            get { return this.createLinksForTitles; }
            set { this.createLinksForTitles = value; }
        }

        private bool appendHeadLine = true;  // 编译时自动添加页头水平线。
        /// <summary>
        /// 是否在编译网页时自动添加页头水平线。
        /// </summary>
        public bool AppendHeadLine
        {
            get { return this.appendHeadLine; }
            set { this.appendHeadLine = value; }
        }

        private bool appendFootLine = true;  // 编译时自动添加页脚水平线。
        /// <summary>
        /// 是否在编译网页时自动添加页脚水平线。
        /// </summary>
        public bool AppendFootLine
        {
            get { return this.appendFootLine; }
            set { this.appendFootLine = value; }
        }

        private bool appendThemeSwitcher = true;

        public bool AppendThemeSwitcher
        {
            get { return this.appendThemeSwitcher; }
            set { this.appendThemeSwitcher = value; }
        }

        private bool appendTimeOfCompiling = false;
        /// <summary>
        /// 是否在编译的 Html 文档末尾添加编译时间。
        /// </summary>
        public bool AppendTimeOfCompiling
        {
            get { return this.appendTimeOfCompiling; }
            set { this.appendTimeOfCompiling = value; }
        }

        private bool imageTitleAtTop = false;
        /// <summary>
        /// 是否将图的标题编译到顶部。默认底部。
        /// </summary>
        public bool ImageTitleAtTop
        {
            get { return this.imageTitleAtTop; }
            set { this.imageTitleAtTop = value; }
        }

        private bool tableCaptionAtBottom = false;
        /// <summary>
        /// 是否将表格标题编译到表格底部。默认顶部。
        /// </summary>
        public bool TableCaptionAtBottom
        {
            get { return this.tableCaptionAtBottom; }
            set { this.tableCaptionAtBottom = value; }
        }

        private bool formatAfterCompile = false;
        /// <summary>
        /// 是否对编译后的Html文档进行格式化。
        /// </summary>
        public bool FormatAfterCompile
        {
            get { return this.formatAfterCompile; }
            set { this.formatAfterCompile = value; }
        }

        private bool isExamEnabled = true;
        /// <summary>
        /// 是否启用“试题编辑”功能。
        /// </summary>
        public bool IsExamEnabled
        {
            get
            {
                return isExamEnabled;
            }

            set
            {
                isExamEnabled = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me == null) continue;

                    me.IsExamEnabled = value;
                }

                if (isExamEnabled)
                {
                    mExams.Visibility = Visibility.Visible;
                    tabValidateDocument.Visibility = Visibility.Visible;
                }
                else
                {
                    mExams.Visibility = Visibility.Collapsed;
                    tabValidateDocument.Visibility = Visibility.Collapsed;
                }
            }
        }

        private bool isAutoCompletionEnabled = false;
        /// <summary>
        /// 是否启用“自动完成”功能。
        /// </summary>
        public bool IsAutoCompletionEnabled
        {
            get
            {
                return isAutoCompletionEnabled;
            }

            set
            {
                isAutoCompletionEnabled = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me == null) continue;

                    me.IsAutoCompletionEnabled = value;
                }
            }
        }

        private bool isEnToChineseDictEnabled = false;
        /// <summary>
        /// 英译中词典条目自动提示功能是否启用的开关。
        /// </summary>
        public bool IsEnToChineseDictEnabled
        {
            get
            {
                return isEnToChineseDictEnabled;
            }

            set
            {
                isEnToChineseDictEnabled = value;

                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsEnToChineseDictEnabled = value;
                }

                if (value)
                {
                    LoadDictionary();
                }
            }
        }

        private MarkdownEditorBase.HighlightingType highlightingSetting = MarkdownEditorBase.HighlightingType.Advance;
        /// <summary>
        /// 采用何种高亮显示方案。
        /// </summary>
        public MarkdownEditorBase.HighlightingType HighlightingSetting
        {
            get { return highlightingSetting; }
            set
            {
                highlightingSetting = value;

                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.HighlightingSetting = value;
                }
            }
        }

        private bool isShowSpaces = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowSpaces
        {
            get { return isShowSpaces; }
            set
            {
                isShowSpaces = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowSpaces = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowSpaces = value;
                }
            }
        }

        private bool isShowEndOfLine = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowEndOfLine
        {
            get { return isShowEndOfLine; }
            set
            {
                isShowEndOfLine = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowEndOfLine = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowEndOfLine = value;
                }
            }
        }

        private bool isShowTabs = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowTabs
        {
            get { return isShowTabs; }
            set
            {
                isShowTabs = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowTabs = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowTabs = value;
                }
            }
        }

        private bool isShowLineNumbers = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowLineNumbers
        {
            get { return isShowLineNumbers; }
            set
            {
                isShowLineNumbers = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowLineNumbers = value;
                }

                foreach (var i in tcRightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowLineNumbers = value;
                }
            }
        }


        private bool showTitleInWorkspaceManager = true;
        /// <summary>
        /// 在工作区管理器中是显示文件短名还是显示文档中设置的“标题”文本。
        /// 文件短名利于看出顺序；标题则更美观些，更直观。
        /// </summary>
        public bool ShowTitleInWorkspaceManager
        {
            get { return showTitleInWorkspaceManager; }
            set
            {
                showTitleInWorkspaceManager = value;
                foreach (var item in tvWorkDirectory.Items)
                {
                    var ti = item as TreeViewItem;
                    if (ti != null)
                    {
                        RefreshWorkspaceTreeViewItemTitles(ti, value);
                    }
                }
            }
        }

        /// <summary>
        /// 刷新工作区管理器中所有项目的标题。
        /// </summary>
        public void RefreshWorkspaceTreeViewItemTitles(TreeViewItem item, bool value)
        {
            var wi = item as WorkspaceTreeViewItem;
            if (wi != null)
            {
                wi.ShowTitle = value;
            }

            foreach (var subItem in item.Items)
            {
                var sti = subItem as TreeViewItem;
                if (sti != null)
                {
                    RefreshWorkspaceTreeViewItemTitles(sti, value);
                }
            }
        }

        /// <summary>
        /// 载入词典条目。
        /// </summary>
        private void LoadDictionary()
        {
            dictionary.Clear();
            LoadDictionaryFromFile(Globals.PathOfUserDictionary);
            LoadDictionaryFromFile(Globals.PathOfWorkspaceDictionary);

            foreach (var i in this.mainTabControl.Items)
            {
                var edit = i as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.InvalidateCompletionWindow();
            }
        }

        /// <summary>
        /// 从指定词典文件中载入词典。
        /// </summary>
        /// <param name="filePath">词典文件路径。</param>
        private void LoadDictionaryFromFile(string filePath)
        {
            if (File.Exists(filePath) == false) return;

            using (StreamReader sr = new StreamReader(filePath))
            {
                var line = sr.ReadLine();
                var splitter = new char[] { '\t' };
                while (string.IsNullOrEmpty(line) == false)
                {
                    if (line.StartsWith(";"))//以分号开头的是注释行
                    {
                        line = sr.ReadLine();
                        continue;
                    }

                    EnToChEntry entry = new EnToChEntry();
                    string[] pieces = line.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
                    int i = 0;
                    if (i < pieces.Length) entry.English = pieces[i];
                    i = 1;
                    if (i < pieces.Length) entry.Chinese = pieces[i];
                    i = 2;
                    if (i < pieces.Length) entry.Alpha = pieces[i];
                    i = 3;
                    if (i < pieces.Length) entry.Comment = pieces[i];
                    dictionary.Add(entry);
                    line = sr.ReadLine();
                }
            }
        }

        /// <summary>
        /// 切换“SelectCellFirst”属性的值。此属性的值将决定
        /// 在编辑文档时，按下Tab键的行为（在文字表中是选选中当前单元格所有文本还是直接跳转到后一单元格）。
        /// </summary>
        private void miSelectCellFirst_Click(object sender, RoutedEventArgs e)
        {
            miSelectCellFirst.IsChecked = !miSelectCellFirst.IsChecked;
            this.SelectCellFirst = miSelectCellFirst.IsChecked;
            App.ConfigManager.Set("SelectCellFirst", miSelectCellFirst.IsChecked.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miAutoCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            if (CompilePageMenu)
            {
                var result = LMessageBox.Show("【自动折叠标题】选项会导致左边栏菜单中的链接不起作用（除非用户展开这些标题）。\r\n\r\n　　真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result != MessageBoxResult.Yes) return;
            }

            miAutoCollapseHtmlHeaders.IsChecked = true;
            miManualCollapseHtmlHeaders.IsChecked = false;
            miNoCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.Auto;
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "AutoCollapseHtmlHeaders",
                HtmlOptionText = "自动折叠标题",
            });
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miManualCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miManualCollapseHtmlHeaders.IsChecked = true;
            miAutoCollapseHtmlHeaders.IsChecked = false;
            miNoCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.Manual;
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "HtmlHeadersCollapse",
                HtmlOptionText = "手动折叠标题",
            });
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miNoCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miNoCollapseHtmlHeaders.IsChecked = true;
            miAutoCollapseHtmlHeaders.IsChecked = false;
            miManualCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.No;
            App.WorkspaceConfigManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "HtmlHeadersCollapse",
                HtmlOptionText = "不折叠标题",
            });
        }

        /// <summary>
        /// 更改当前工作区目录。
        /// </summary>
        private void btnSetWorkspace_Click(object sender, RoutedEventArgs e)
        {
            ChangeWorkspace();
        }

        private void btnCopyWorkspaceStruct_Click(object sender, RoutedEventArgs e)
        {
            CopyStructAndCreateNewWorkspace();
        }

        #region 搜索资源

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private void tbxSearchResource_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                SearchInWorkspace();
                e.Handled = true;
            }
        }

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        internal void SearchInWorkspace()
        {
            if (miSearchTitle.IsChecked == false && miSearchDirectoryOrFileName.IsChecked == false)
            {
                var result = LMessageBox.Show("【按标题搜索】或【按目录/文件短名搜索】这两个菜单项至少要选中一个！否则无法执行。\r\n\r\n　　要自动全部勾选吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result != MessageBoxResult.Yes) return;

                miSearchTitle.IsChecked = true;
                miSearchDirectoryOrFileName.IsChecked = true;
            }

            searchedWorkspaceItemList.Clear();
            searchedWorkspaceItemsIndex = -1;

            this.cmbSearchResource.Items.Insert(0, cmbSearchResource.Text);

            if (SearchFromWorkspaceItem(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, this.cmbSearchResource.Text))
            {
                if (searchedWorkspaceItemList.Count >= 0)
                {
                    searchedWorkspaceItemsIndex += 1;
                    var child = searchedWorkspaceItemList[searchedWorkspaceItemsIndex];
                    child.IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedWorkspaceItemList[searchedWorkspaceItemsIndex]);

                    //选中第一项并显示之。
                    child.BringIntoView();
                    tvWorkDirectory.Focus();
                }
            }

            if (tcManagerPanels.SelectedItem != tiWorkspace)
                tcManagerPanels.SelectedItem = tiWorkspace;
        }

        /// <summary>
        /// 在工作区管理器中某个条目开始向下递归查找指定的条目。
        /// </summary>
        private bool SearchFromWorkspaceItem(WorkspaceTreeViewItem item, string findKeyWord)
        {
            try
            {
                if (string.IsNullOrEmpty(findKeyWord)) return false;

                if (item == null) return false;

                bool finded = false;

                if (string.IsNullOrEmpty(item.ShortName)) return false;

                var lowerKeyword = findKeyWord.ToLower();

                if (miFindWorkspaceItemWithRegex.IsChecked == true)
                {
                    Regex reg = new Regex(findKeyWord);
                    var matchShortName = reg.Match(item.ShortName);
                    var matchTitle = reg.Match(item.Title);

                    if (miSearchTitle.IsChecked == true && miSearchDirectoryOrFileName.IsChecked == true)
                    {
                        if (matchTitle.Success || matchShortName.Success)
                        {
                            searchedWorkspaceItemList.Add(item);
                            finded = true;
                        }
                    }
                    else if (miSearchTitle.IsChecked != true && miSearchDirectoryOrFileName.IsChecked == true)
                    {
                        if (matchShortName.Success)
                        {
                            searchedWorkspaceItemList.Add(item);
                            finded = true;
                        }
                    }
                    else if (miSearchTitle.IsChecked == true && miSearchDirectoryOrFileName.IsChecked != true)
                    {
                        if (matchTitle.Success)
                        {
                            searchedWorkspaceItemList.Add(item);
                            finded = true;
                        }
                    }
                    // else 两个都不选是不会执行到这里的。
                }
                else
                {
                    if (miSearchTitle.IsChecked == true && miSearchDirectoryOrFileName.IsChecked == true)
                    {
                        if (item.ShortName.ToLower().Contains(lowerKeyword) ||
                            item.Title.ToLower().Contains(lowerKeyword) ||
                            (miFindWorkspaceItemWithFirstLetter.IsChecked == true && ChinesePinYin.ToChinesePinYinText(item.Title).ToLower().Contains(lowerKeyword)))
                        {
                            searchedWorkspaceItemList.Add(item);
                            finded = true;
                        }
                    }
                    else if (miSearchTitle.IsChecked != true && miSearchDirectoryOrFileName.IsChecked == true)
                    {
                        if (item.ShortName.ToLower().Contains(lowerKeyword))
                        {
                            searchedWorkspaceItemList.Add(item);
                            finded = true;
                        }
                    }
                    else if (miSearchTitle.IsChecked == true && miSearchDirectoryOrFileName.IsChecked != true)
                    {
                        if (item.Title.ToLower().Contains(lowerKeyword) ||
                            (miFindWorkspaceItemWithFirstLetter.IsChecked == true && ChinesePinYin.ToChinesePinYinText(item.Title).ToLower().Contains(lowerKeyword)))
                        {
                            searchedWorkspaceItemList.Add(item);
                            finded = true;
                        }
                    }
                    // else 两个都不选是不会执行到这里的。
                }

                if (item.Items.Count > 0)
                {
                    foreach (var it in item.Items)
                    {
                        var subFined = SearchFromWorkspaceItem(it as WorkspaceTreeViewItem, findKeyWord);
                        finded = subFined || finded;
                    }
                }

                return finded;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        private List<WorkspaceTreeViewItem> searchedWorkspaceItemList = new List<WorkspaceTreeViewItem>();

        internal List<WorkspaceTreeViewItem> SearchedWirkspaceItemlist { get { return searchedWorkspaceItemList; } }

        /// <summary>
        /// 用来在切换当前找到的资源时定位。
        /// </summary>
        private int searchedWorkspaceItemsIndex = -1;

        /// <summary>
        /// 查找指定的资源文件。
        /// </summary>
        private void btnSearchInWorkspace_Clicked(object sender, RoutedEventArgs e)
        {
            SearchInWorkspace();
        }

        /// <summary>
        /// 在工作区管理器中选定上一个找到的资源条目。
        /// </summary>
        private void btnSearchPreview_Clicked(object sender, RoutedEventArgs e)
        {
            SelectPreviewSearchedWorkspaceItem();
            e.Handled = true;//避免失焦
        }

        /// <summary>
        /// 在工作区管理器中选定上一个找到的资源条目。
        /// </summary>
        private void SelectPreviewSearchedWorkspaceItem()
        {
            if (searchedWorkspaceItemsIndex > 0)
            {
                searchedWorkspaceItemsIndex -= 1;
                if (searchedWorkspaceItemsIndex >= 0 && searchedWorkspaceItemsIndex < searchedWorkspaceItemList.Count)
                {
                    var child = searchedWorkspaceItemList[searchedWorkspaceItemsIndex];
                    child.IsSelected = true;
                    child.BringIntoView();
                    ExpandWorkspaceTreeviewItem(searchedWorkspaceItemList[searchedWorkspaceItemsIndex]);

                    tvWorkDirectory.Focus();
                }
            }
        }

        /// <summary>
        /// 在工作区管理器中选定下一个找到的资源条目。
        /// </summary>
        private void btnSearchNext_Clicked(object sender, RoutedEventArgs e)
        {
            SelectNextSearchedWorkspaceItem();
            e.Handled = true;//避免失焦
        }

        /// <summary>
        /// 在工作区管理器中选定下一个找到的资源条目。
        /// </summary>
        private void SelectNextSearchedWorkspaceItem()
        {
            if (searchedWorkspaceItemsIndex < searchedWorkspaceItemList.Count - 1)
            {
                searchedWorkspaceItemsIndex += 1;
                if (searchedWorkspaceItemsIndex >= 0 && searchedWorkspaceItemsIndex < searchedWorkspaceItemList.Count)
                {
                    var child = searchedWorkspaceItemList[searchedWorkspaceItemsIndex];
                    child.IsSelected = true;
                    child.BringIntoView();
                    ExpandWorkspaceTreeviewItem(searchedWorkspaceItemList[searchedWorkspaceItemsIndex]);
                    tvWorkDirectory.Focus();
                }
            }
        }

        /// <summary>
        /// 展开指定的工作区管理器中的某个条目。
        /// </summary>
        /// <param name="item">要展开的某个条目，其所有上级都会被展开。</param>
        private void ExpandWorkspaceTreeviewItem(WorkspaceTreeViewItem item)
        {
            if (item == null) return;

            item.IsExpanded = true;

            var parent = item.ParentWorkspaceTreeViewItem;
            while (parent != null)
            {
                parent.IsExpanded = true;
                parent = parent.ParentWorkspaceTreeViewItem;
            }
        }
        #endregion

        /// <summary>
        /// 在查找框中按回车键执行查找操作。
        /// </summary>
        private void cmbFindText_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (cmbSearchArea.SelectedIndex == 0)
                {
                    btnFind_Click(sender, e);
                }
                else
                {
                    FindText(tvFindAndReplace);
                }
            }
        }

        /// <summary>
        /// 在替换框中按回车键执行替换操作。
        /// </summary>
        private void cmbReplaceTextInputBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (cmbSearchArea.SelectedIndex == 0)
                {
                    if ((Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0 ||
                        (Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0)
                    {
                        btnReplaceAll_Click(sender, e);
                    }
                    else
                    {
                        btnReplace_Click(sender, e);
                    }
                    e.Handled = true;
                }
            }
        }

        /// <summary>
        /// 根据“查找范围”框中定义的查找范围来查找“查找”框中指定的文本。
        /// </summary>
        /// <param name="treeView">指定将查找到的结果显示在哪个树型框中。</param>
        public void FindText(TreeView treeView)
        {
            treeView.Items.Clear();
            FindText(cmbFindText.Text, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), false, treeView);

            if (treeView.Items.Count <= 0)
            {
                treeView.Items.Add(new TreeViewItem() { Header = "<没找到结果...>", });
            }

            cmbFindText.Items.Add(cmbFindText.Text);

            if (tcRightToolBar.SelectedItem != tiFindResult)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 在磁盘文档或当前正在编辑的文档之间查找指定文本内容。
        /// </summary>
        /// <param name="destText">如果指定了此参数，则按此参数查找。</param>
        /// <param name="preChar">前导字符，决定找到指定的文本后，双击后要选定的文本区域要向前移动到哪个字符。</param>
        /// <param name="nextChar">后缀字符，决定找到指定的文本后，双击后要选定的文本区域要向后移动到哪个字符。</param>
        private void FindText(string keyWord, string searchArea, bool forceRegex, TreeView treeView)
        {
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                if (treeView == null) treeView = tvFindAndReplace;

                treeView.Items.Clear();

                int keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@" || keyWord == @"\[.*\]\(@.*\)")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                switch (searchArea)
                {
                    case "ActiveDocument":
                        {
                            FindTextInActiveDocument(keyWord, forceRegex, treeView, type);
                            break;
                        }
                    case "OpenedDocuments":
                        {
                            FindTextInOpenedDocuments(keyWord, forceRegex, treeView, type);
                            break;
                        }
                    case "AllFiles":
                        {
                            //这个需要用到递归
                            //FindTextInAllFiles(Globals.PathOfWorkspace, keyWord, forceRegex, treeView);
                            if (Globals.MainWindow.tvWorkDirectory.Items.Count > 0)
                            {
                                var rootItem = Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
                                if (rootItem != null)
                                    FindTextFromWorkspaceTreeViewItem(rootItem, keyWord, forceRegex, treeView);
                            }
                            break;
                        }
                    case "CheckedFiles":
                        {
                            if (Globals.MainWindow.tvWorkDirectory.Items.Count > 0)
                            {
                                var rootItem = Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
                                if (rootItem != null)
                                    FindTextFromWorkspaceTreeViewItem(rootItem, keyWord, forceRegex, treeView, true);
                            }
                            break;
                        }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在打开的所有文档中查找文本。
        /// </summary>
        /// <param name="keyWord">要查找的关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式来查找。</param>
        /// <param name="treeView">将查找的结果放在哪个树型框中呈现。</param>
        /// <param name="type">指定要查找什么类型的文本，树型框的条目会呈现为不同的姿态。</param>
        private void FindTextInOpenedDocuments(string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    Regex regex = GetRegEx(keyWord, false, forceRegex);
                    var matches = regex.Matches(efi.EditorBase.Text);

                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                            var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                            fdi.Items.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                                match.Index - line.Offset, match.Length, lineText, null, type));
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找文本。
        /// </summary>
        /// <param name="keyWord">要查找的关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式来查找。</param>
        /// <param name="treeView">将查找的结果放在哪个树型框中呈现。</param>
        /// <param name="type">指定要查找什么类型的文本，树型框的条目会呈现为不同的姿态。</param>
        private void FindTextInActiveDocument(string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);

                Regex regex = GetRegEx(keyWord, false, forceRegex);
                var matches = regex.Matches(efi.EditorBase.Text);

                foreach (Match match in matches)
                {
                    if (match.Success)
                    {
                        var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        fdi.Items.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                            match.Index - line.Offset, match.Length, lineText, null, type));
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }


        /// <summary>
        /// [递归方法]在指定目录下的所有 Markdown 文件中查找指定文本。
        /// </summary>
        /// <param name="directoryPath">基准目录，从该目录开始查找。</param>
        /// <param name="keyWord">查找关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式方式查找。</param>
        /// <param name="treeView">将查找结果显示在哪个树型框中。</param>
        private void FindTextFromWorkspaceTreeViewItem(WorkspaceTreeViewItem wtvi, string keyWord, bool forceRegex, TreeView treeView, bool onlyCheckedFiles = false)
        {
            if (wtvi == null) return;
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                var keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                if (wtvi.IsMarkdownFilePath)  // 普通 Markdown 文件条目
                {
                    if (onlyCheckedFiles)
                    {
                        if (wtvi.IsChecked == true)
                        {
                            FindTextInFile(wtvi, keyWord, forceRegex, treeView, type, wtvi.FullPath);
                        }
                        // else ignore
                    }
                    else
                    {
                        FindTextInFile(wtvi, keyWord, forceRegex, treeView, type, wtvi.FullPath);
                    }
                }
                else if (wtvi.IsMetaDirectoryPath)  // 普通目录条目
                {
                    //先查找目录元文件
                    var metaFilePath = wtvi.MetaFilePath;
                    if (onlyCheckedFiles)
                    {
                        if (wtvi.IsChecked == true)
                        {
                            FindTextInFile(wtvi, keyWord, forceRegex, treeView, type, metaFilePath);
                        }
                        //else ignore
                    }
                    else
                    {
                        FindTextInFile(wtvi, keyWord, forceRegex, treeView, type, metaFilePath);
                    }

                    //再递归处理子目录下的文件
                    foreach (var item in wtvi.Items)
                    {
                        var childItem = item as WorkspaceTreeViewItem;
                        FindTextFromWorkspaceTreeViewItem(childItem, keyWord, forceRegex, treeView, onlyCheckedFiles);
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void FindTextInFile(WorkspaceTreeViewItem wtvi, string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type, string metaFilePath)
        {
            if (File.Exists(metaFilePath))
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(metaFilePath,
                    new FileInfo(metaFilePath).Name, wtvi.Title);
                var fileEditor = GetOpenedEditor(metaFilePath);
                if (fileEditor != null)
                {
                    Regex regex = GetRegEx(keyWord, false, forceRegex);
                    var matches = regex.Matches(fileEditor.EditorBase.Text);

                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var line = fileEditor.EditorBase.Document.GetLineByOffset(match.Index);
                            var lineText = fileEditor.EditorBase.Document.GetText(line.Offset, line.Length);
                            fdi.Items.Add(new FindLineTreeViewItem(fileEditor.FullFilePath, fileEditor.ShortFileName, line.LineNumber,
                                match.Index - line.Offset, match.Length, lineText, null, type));
                        }
                    }
                }
                else
                {
                    Regex regex = GetRegEx(keyWord, false, forceRegex);
                    var fileContent = File.ReadAllText(metaFilePath);
                    var matches = regex.Matches(fileContent);

                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var preLineEndMarkEndIndex = Math.Max(fileContent.LastIndexOf("\r", match.Index), fileContent.LastIndexOf("\n", match.Index));

                            var matchEndIndex = match.Index + match.Length + 1;  // match.Value 是以 '\r' 结束的。
                            var nextLineEndStartIndex = Math.Min(fileContent.IndexOf("\r", matchEndIndex), fileContent.IndexOf("\n", matchEndIndex));
                            var lineNumber = 1;
                            for (int i = 0; i < match.Index; i++)
                            {
                                if (fileContent[i] == '\n') { lineNumber++; }
                            }

                            string lineText;
                            if (nextLineEndStartIndex - preLineEndMarkEndIndex > 0)
                            {
                                if (preLineEndMarkEndIndex < 0)
                                {
                                    if (nextLineEndStartIndex < 0)
                                    {
                                        lineText = fileContent;
                                    }
                                    else
                                    {
                                        lineText = fileContent.Substring(0, nextLineEndStartIndex);
                                    }
                                }
                                else
                                {
                                    lineText = fileContent.Substring(preLineEndMarkEndIndex + 1, nextLineEndStartIndex - preLineEndMarkEndIndex - 1);
                                }
                            }
                            else
                            {
                                lineText = match.Value;
                            }

                            fdi.Items.Add(new FindLineTreeViewItem(metaFilePath, new FileInfo(metaFilePath).Name, lineNumber,
                               Math.Max(0, match.Index - preLineEndMarkEndIndex - 1), match.Length, lineText, null, type));
                        }
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }
        }


        /// <summary>
        /// [递归方法]在指定目录下的所有 Markdown 文件中查找指定文本。
        /// </summary>
        /// <param name="directoryPath">基准目录，从该目录开始查找。</param>
        /// <param name="keyWord">查找关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式方式查找。</param>
        /// <param name="treeView">将查找结果显示在哪个树型框中。</param>
        private void FindTextInAllFiles(string directoryPath, string keyWord, bool forceRegex, TreeView treeView)
        {
            if (Directory.Exists(directoryPath) == false) return;
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                var keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                var directory = new DirectoryInfo(directoryPath);

                //先处理当前目录下的文件
                var childFilesInfos = directory.GetFiles();

                foreach (var childFileInfo in childFilesInfos)
                {
                    if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                    //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                    var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                    if (fileEditor != null)
                    {
                        Regex regex = GetRegEx(keyWord, false, forceRegex);
                        var matches = regex.Matches(fileEditor.EditorBase.Text);

                        foreach (Match match in matches)
                        {
                            if (match.Success)
                            {
                                var line = fileEditor.EditorBase.Document.GetLineByOffset(match.Index);
                                var lineText = fileEditor.EditorBase.Document.GetText(line.Offset, line.Length);
                                fdi.Items.Add(new FindLineTreeViewItem(fileEditor.FullFilePath, fileEditor.ShortFileName, line.LineNumber,
                                    match.Index - line.Offset, match.Length, lineText, null, type));
                            }
                        }
                    }
                    else
                    {
                        Regex regex = GetRegEx(keyWord, false, forceRegex);
                        var fileContent = File.ReadAllText(childFileInfo.FullName);
                        var matches = regex.Matches(fileContent);

                        foreach (Match match in matches)
                        {
                            if (match.Success)
                            {
                                var preLineEndMarkEndIndex = Math.Max(fileContent.LastIndexOf("\r", match.Index), fileContent.LastIndexOf("\n", match.Index));

                                var matchEndIndex = match.Index + match.Length + 1;  // match.Value 是以 '\r' 结束的。
                                var nextLineEndStartIndex = Math.Min(fileContent.IndexOf("\r", matchEndIndex), fileContent.IndexOf("\n", matchEndIndex));
                                var lineNumber = 1;
                                for (int i = 0; i < match.Index; i++)
                                {
                                    if (fileContent[i] == '\n') { lineNumber++; }
                                }

                                string lineText;
                                if (nextLineEndStartIndex - preLineEndMarkEndIndex > 0)
                                {
                                    if (preLineEndMarkEndIndex < 0)
                                    {
                                        if (nextLineEndStartIndex < 0)
                                        {
                                            lineText = fileContent;
                                        }
                                        else
                                        {
                                            lineText = fileContent.Substring(0, nextLineEndStartIndex);
                                        }
                                    }
                                    else
                                    {
                                        lineText = fileContent.Substring(preLineEndMarkEndIndex + 1, nextLineEndStartIndex - preLineEndMarkEndIndex - 1);
                                    }
                                }
                                else
                                {
                                    lineText = match.Value;
                                }

                                fdi.Items.Add(new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name, lineNumber,
                                   Math.Max(0, match.Index - preLineEndMarkEndIndex - 1), match.Length, lineText, null, type));
                            }
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                    }
                }

                //再递归处理子目录下的文件
                var childDirectories = directory.GetDirectories();
                foreach (var childDirectory in childDirectories)
                {
                    FindTextInAllFiles(childDirectory.FullName, keyWord, forceRegex, treeView);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 根据“查找范围”框中定义的查找范围来查找“查找”框中指定的文本。
        /// </summary>
        private void btnFindText_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            FindText(tvFindAndReplace);
        }

        /// <summary>
        /// 折叠当前活动编辑器中选择区所在的折叠块。
        /// </summary>
        private void miFoldThisBlock_Click(object sender, RoutedEventArgs e)
        {
            if (Globals.MainWindow.SupportFolding == false) return;

            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            efi.EditorBase.FoldSelectedBlock();
        }

        /// <summary>
        /// 折叠当前活动编辑器中所有可折叠区域。
        /// </summary>
        private void miFoldingAll_Click(object sender, RoutedEventArgs e)
        {
            if (Globals.MainWindow.SupportFolding == false) return;

            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
            {
                i.IsFolded = true;
            }
        }

        /// <summary>
        /// 展开当前活动编辑器中所有的折叠区域。
        /// </summary>
        private void miUnFoldingAll_Click(object sender, RoutedEventArgs e)
        {
            if (Globals.MainWindow.SupportFolding == false) return;

            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
            {
                i.IsFolded = false;
            }
        }

        /// <summary>
        /// 向指定目录导出当前工作区下所有 Html 文件及其资源文件。
        /// </summary>
        private void miOutportWebSite_Click(object sender, RoutedEventArgs e)
        {
            string destOutportFolder;

            //选择新工作目录
            System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog()
            {
                Description = "　　请选择一个文件夹（目录）作为导出目标。\r\n　　导出时会简单地将除Markdown文件以外的所有文件复制到目标文件夹中。",
                ShowNewFolderButton = true,
            };
            System.Windows.Interop.HwndSource source = PresentationSource.FromVisual(this) as System.Windows.Interop.HwndSource;

            System.Windows.Forms.IWin32Window win = new WinFormWindow(source.Handle);
            System.Windows.Forms.DialogResult result = fbd.ShowDialog(win);
            if (result.Equals(System.Windows.Forms.DialogResult.OK))
            {
                destOutportFolder = fbd.SelectedPath;
            }
            else return;

            OutportFiles(destOutportFolder);
        }

        /// <summary>
        /// 向指定目录导出当前工作区下所有 Html 文件及其资源文件。
        /// </summary>
        /// <param name="destOutportFolder">指定的导出目标目录的绝对路径。</param>
        private void OutportFiles(string destOutportFolder)
        {
            if (Directory.Exists(destOutportFolder) == false)
            {
                LMessageBox.Show("指定目录不存在！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (destOutportFolder[destOutportFolder.Length - 1] != Path.DirectorySeparatorChar)
            {
                destOutportFolder += Path.DirectorySeparatorChar;
            }

            try
            {
                bool compileBeforeOutport = false;

                var result = LMessageBox.Show("导出前要重新编译所有Markdown文件吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    compileBeforeOutport = true;
                }

                List<FileSystemEntry> fileSystemEntries = new List<FileSystemEntry>();

                CopyCompiledDirectory(Globals.PathOfWorkspace, destOutportFolder, compileBeforeOutport, ref fileSystemEntries);

                //生成一个html索引文档。
                //var indexFilePath = BuildIndexOfCompiledHtmlFiles(fileSystemEntries);   //旧版，根据目录
                var indexFilePath = BuildIndexOfCompiledHtmlFiles();                      //新版，根据工作区管理器布局

                if (File.Exists(destOutportFolder + "_Index.html") == false)
                {
                    File.Copy(indexFilePath, destOutportFolder + "_Index.html");
                }

                // var lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);

                if (destOutportFolder.EndsWith("\\"))
                {
                    destOutportFolder = destOutportFolder.Substring(0, destOutportFolder.Length - 1);
                }

                destOutportFolder = destOutportFolder.ToLower();

                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    string[] lines = File.ReadAllLines(Globals.PathOfHistoryOutputFileFullName, new UnicodeEncoding());
                    var newLinesList = new List<string>();
                    foreach (var line in lines)
                    {
                        string tmp;
                        if (line.EndsWith("\\"))
                        {
                            tmp = line.Substring(0, line.Length - 1).ToLower();
                        }
                        else tmp = line.ToLower();

                        if (tmp == destOutportFolder) continue;

                        newLinesList.Add(line);
                    }

                    var newLines = new string[newLinesList.Count + 1];
                    newLines[0] = destOutportFolder;
                    for (int i = 1; i < newLines.Length; i++)
                    {
                        newLines[i] = newLinesList[i - 1];
                    }

                    File.WriteAllLines(Globals.PathOfHistoryOutputFileFullName, newLines, new UnicodeEncoding());
                }
                else
                {
                    File.WriteAllLines(Globals.PathOfHistoryOutputFileFullName, new string[] { destOutportFolder }, new UnicodeEncoding());
                }

                for (int i = lbxHistoryOutport.Items.Count - 1; i >= 0; i--)
                {
                    var rwitem = lbxHistoryOutport.Items[i] as RecentDirectoryListBoxItem;
                    if (rwitem != null && rwitem.DirectoryPath == destOutportFolder)
                    {
                        lbxHistoryOutport.Items.Remove(rwitem);
                    }
                }

                var row = new RecentDirectoryListBoxItem(destOutportFolder)
                {
                    ToolTip = "双击将编译的所有 Html 文件及资源文件导出到该目录",
                };
                row.MouseDoubleClick += row_MouseDoubleClick;

                lbxHistoryOutport.Items.Insert(0, row);
                lbxHistoryOutport.SelectedIndex = 0;

                tcManagerPanels.SelectedItem = tiWorkspace;
                RefreshOutputHistoryList();

                //LMessageBox.Show("已将工作区目录下所有非Markdown文件（目录）导出到以下磁盘位置：\r\n　　" +
                //    destOutportFolder, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);

                System.Diagnostics.Process.Start("explorer.exe", $"\"{destOutportFolder}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        /// <summary>
        /// 刷新“历史导出列表”。
        /// </summary>
        private void RefreshOutputHistoryList()
        {
            for (int i = 0; i < lbxHistoryOutport.Items.Count; i++)
            {
                var item = lbxHistoryOutport.Items[i] as RecentDirectoryListBoxItem;
                if (item == null) continue;

                item.IndexText = (i + 1).ToString();
            }
        }


        /// <summary>
        /// 为编译的所有 Html 创建一个带目录结构的 Html 索引文件。
        /// </summary>
        /// <param name="fileSystemEntries">编译过的所有 Html 文件的路径信息。</param>
        /// <returns>返回 Html 索引文件的内容文本。</returns>
        private string BuildIndexOfCompiledHtmlFiles(List<FileSystemEntry> fileSystemEntries)
        {
            if (fileSystemEntries.Count > 0)
            {
                var charSetText = "UTF-8";
                var encoding = Encoding.UTF8;
                if (miGb2312.IsChecked)
                {
                    charSetText = "GB2312";
                    encoding = Encoding.GetEncoding("gb2312");
                }
                StringBuilder sbIndex = new StringBuilder();

                var title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);

                sbIndex.Append("<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n"
                    + "<meta name=\"viewport\" content=\"width=device-width, user-scalable= yes\"/>\n"
                    + $"<meta http-equiv=\"Content-Type\" content=\"text/html; charset={charSetText}\">\n"
                    + $"<title>{title}</title>\n<link rel=\"stylesheet\" href=\"./lesson_"
                    + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\"></head>\n<body>"
                    + $"<p class='fileheader' id='file_header'>{title}</p><hr />"
                    + "\n");

                int tmpIndex = Globals.PathOfWorkspace.Length;
                string preDirectory = "/";

                foreach (FileSystemEntry fileSystemEntry in fileSystemEntries)
                {
                    string prefix;
                    if (fileSystemEntry.FileFullName.StartsWith("directory_entry:///"))
                    {
                        //目录
                        preDirectory = fileSystemEntry.FileFullName.Substring(19).Substring(Globals.PathOfWorkspace.Length).Replace("\\", "/");
                        if (preDirectory.EndsWith("/") == false) preDirectory += "/";

                        var pieces = preDirectory.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                        var sb = new StringBuilder();
                        foreach (var p in pieces)
                        {
                            sb.Append(" >> ");
                            sb.Append(FormatDocumentTitle(p));
                        }
                        var preDirectoryText = sb.ToString();
                        prefix = BuildPrefix(preDirectory, true);

                        DirectoryInfo di = new DirectoryInfo(fileSystemEntry.FileFullName.Substring(19));
                        var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".html";
                        if (File.Exists(metaFilePath))
                        {
                            FileInfo metaFileInfo = new FileInfo(metaFilePath);

                            string tmpPath2 = di.FullName;
                            if (di.FullName.StartsWith(Globals.PathOfWorkspace))
                            {
                                tmpPath2 = /*destPath + */ di.FullName.Substring(tmpIndex).Replace("\\", "/").Substring(preDirectory.Length - 1);
                                if (tmpPath2.StartsWith("/")) tmpPath2 = tmpPath2.Substring(1);
                            }
                            sbIndex.Append("<p>" + prefix + "<a href=\"./" +
                                (preDirectory == "/" ? "" : preDirectory) + tmpPath2 + metaFileInfo.Name + "\">" + " >> " + GetHtmlFileTile(metaFileInfo) + "</a></p>\n");
                        }
                        else
                        {
                            sbIndex.Append("<p>" + prefix + " >> " + FormatDocumentTitle(di) + " </p>");

                        }
                        continue;
                    }

                    //html文件
                    var fileInfo = new FileInfo(fileSystemEntry.FileFullName);
                    if (fileInfo.Exists == false) continue;

                    if (fileInfo.Name.StartsWith("_") == false && fileInfo.Name.ToLower().EndsWith(".html"))
                    {
                        var fullDirectoryPath = fileInfo.DirectoryName;
                        if (fullDirectoryPath.EndsWith("\\") == false)
                        {
                            fullDirectoryPath += "\\";
                        }

                        if (fullDirectoryPath.StartsWith(Globals.PathOfWorkspace))
                        {
                            preDirectory = fullDirectoryPath.Substring(Globals.PathOfWorkspace.Length).Replace("\\", "/");
                        }
                        else
                        {
                            preDirectory = "";
                        }

                        prefix = BuildPrefix(preDirectory, false);

                        //元文件不在这里添加条目。                       
                        sbIndex.Append("<p>" + prefix + "<a href=\"./" +
                            (preDirectory == "/" ? "" : preDirectory) + fileInfo.Name + "\">" + GetHtmlFileTile(fileInfo) + "</a></p>\n");
                    }
                }

                sbIndex.Append($"<hr /><div class='foot'><p id='compile_time'>{DateTime.Now.ToString()}</p></div></body>\n</html>");

                var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

                File.WriteAllText(indexHtmlFilePath, sbIndex.ToString(), encoding);
                return indexHtmlFilePath;
            }
            else return "";
        }

        /// <summary>
        /// [只读]允许 Python 脚本访问。
        /// </summary>
        public TreeView WorkspaceTreeView { get { return tvWorkDirectory; } }

        /// <summary>
        /// 2017年7月21日新版，改用工作区布局文件来生成，为编译的所有 Html 创建一个带目录结构的 Html 索引文件。
        /// </summary>
        /// <param name="fileSystemEntries">编译过的所有 Html 文件的路径信息。</param>
        /// <returns>返回 Html 索引文件的内容文本。</returns>
        private string BuildIndexOfCompiledHtmlFiles()
        {
            var charSetText = "UTF-8";
            var encoding = Encoding.UTF8;
            if (miGb2312.IsChecked)
            {
                charSetText = "GB2312";
                encoding = Encoding.GetEncoding("gb2312");
            }
            else if (miGb18030.IsChecked)
            {
                charSetText = "GB18030";
                encoding = Encoding.GetEncoding("gb18030");
            }
            StringBuilder sbIndex = new StringBuilder();

            var title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);

            sbIndex.Append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n");
            sbIndex.Append("<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n");
            sbIndex.Append("<!-- saved from url=(0016)http://localhost -->\r\n");
            sbIndex.Append("<head>\r\n");
            sbIndex.Append("<meta name=\"viewport\" content=\"width=device-width, user-scalable= yes\"/>\r\n");
            sbIndex.Append($"<meta http-equiv=\"Content-Type\" content=\"text/html;charset={charSetText}\">\r\n");
            sbIndex.Append($"<meta http-equiv=\"X-UA-Compatible\" content=\"IE={DefaultIeVersion}\"/>\r\n");
            sbIndex.Append($"<title>{title}目录</title>\r\n<link id=\"theme_link\" rel=\"stylesheet\" href=\"./lesson_");
            sbIndex.Append(Globals.MainWindow.ThemeText + ".css\" type=\"text/css\">\r\n" +
                "<link id=\"custom_theme_link\" rel=\"stylesheet\" href=\"./custom_lesson_" +
                Globals.MainWindow.ThemeText + ".css\" type=\"text/css\">" +
                "</head>\r\n");
            sbIndex.Append("<body>\r\n");
            sbIndex.Append($"<p class='fileheader' id='file_header'>{title}目录</p><hr />\r\n");

            //工作区目录本身的链接应单独添加
            DirectoryInfo diWorkspace = new DirectoryInfo(Globals.PathOfWorkspace);
            if (diWorkspace.Exists)
            {
                var workspaceMetaFileShortName = "_" + diWorkspace.Name + ".html";
                var workspaceMetaFilePath = Globals.PathOfWorkspace + workspaceMetaFileShortName;

                if (File.Exists(workspaceMetaFilePath))
                {
                    var workspaceMetaTitle = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);
                    var prefix = "&gt; ";
                    var linkText = $"<p><a href=\"{workspaceMetaFileShortName}\">{prefix}{workspaceMetaTitle}</a></p>";
                    sbIndex.Append(linkText);
                }
            }

            BuildWorkspaceIndexFileLinkTexts(Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, ref sbIndex);

            sbIndex.Append($"<hr /><div class='foot'><p id='compile_time'>{DateTime.Now.ToString()}</p></div><br/><br/>" +
                $"<script>{switchThemeFunc + loadThemeFunc + loadUserSelectedThemeFunc}</script>" +
                $"<div><img id=\"theme_switcher\" src=\"images~/theme_switcher.png\" onclick=\"SwitchTheme()\"/></div>" +
                $"<br/><br/>" +
                $"<script src=\"jquery-1.7.0.min.js\"></script>" +
                $"<script>$(document).ready(LoadUserSelectedTheme());</script>" +
                $"</body>\r\n</html>");

            var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

            var resultHtml = sbIndex.ToString();

            if (FormatAfterCompile)
            {
                resultHtml = CustomMarkdownSupport.FormatHtml(resultHtml).Replace("\n", "\r\n");
            }

            resultHtml = resultHtml.Replace("\r\r", "\r");

            File.WriteAllText(indexHtmlFilePath, resultHtml, encoding);
            return indexHtmlFilePath;
        }

        internal static readonly string switchThemeFunc = @"
function SwitchTheme()
{
    var css = document.getElementById('theme_link');
    var custom_css = document.getElementById('custom_theme_link');
    var url;
    var custom_url;
    var theme = 'light';
	if (/.*_dark.css$/.test(css.getAttribute('href'))) theme = 'dark';
    if (theme == 'light')
    {
        url = './lesson_dark.css';
        custom_url = './custom_lesson_dark.css';
        window.name = 'dark';
    }
    else
    {
        url = './lesson_light.css';
        custom_url = './custom_lesson_light.css';
        window.name = 'light';
    }
    if (css) css.setAttribute('href', url);
    if (custom_css) custom_css.setAttribute('href', custom_url);
}
";
        internal static readonly string loadThemeFunc = @"
function LoadTheme(new_theme)
{
    var css = document.getElementById('theme_link');
    var custom_css = document.getElementById('custom_theme_link');
    var url;
    var custom_url;
    var theme = 'light';
	if (/.*_dark.css$/.test(css.getAttribute('href'))) theme = 'dark';
    if (new_theme && new_theme != theme)
    {
        if (new_theme == 'dark')
        {
            url = './lesson_dark.css';
            custom_url = './custom_lesson_dark.css';
        }
        else
        {
            url = './lesson_light.css';
            custom_url = './custom_lesson_light.css';
        }
        if (css) css.setAttribute('href', url);
        if (custom_css) custom_css.setAttribute('href', custom_url);
    }
}
";

        internal static readonly string loadUserSelectedThemeFunc = @"
function LoadUserSelectedTheme()
{
    if(window.name == 'dark' || window.name == 'light')
    {
        LoadTheme(window.name);
    }
}
";
        /// <summary>
        /// 这是个递归方法。注意：此方法不能为工作区目录本身的目录元文件添加链接！！！
        /// </summary>
        /// <param name="item"></param>
        /// <param name="sbIndex"></param>
        private void BuildWorkspaceIndexFileLinkTexts(WorkspaceTreeViewItem item, ref StringBuilder sbIndex)
        {
            if (item == null) return;
            if (sbIndex == null) return;
            if (Globals.MainWindow.tvWorkDirectory.Items.Count <= 0) return;

            if (item.IsMarkdownFilePath != true && item.IsDirectoryExists != true/*这个条件不能去除！*/) return;
            if (item.FullPath.EndsWith("~") || item.FullPath.EndsWith("~\\")) return;  //资源目录

            if (Globals.MainWindow.IgnoreEncryptedFiles && item.IsEncrypted) return;

            if (Globals.MainWindow.IgnoreAbortedFiles && item.IsAborted) return;

            var level = 1;
            var parentItem = item.ParentWorkspaceTreeViewItem;
            while (parentItem != null)
            {
                if (parentItem == Globals.MainWindow.tvWorkDirectory.Items[0]) break;
                else parentItem = parentItem.ParentWorkspaceTreeViewItem;
                level++;
            }

            var preText = new string('　', level);
            var relativePath = item.RelativeHtmlPath;
            var a = Globals.PathOfWorkspace.ToLower();
            var b = relativePath.ToLower();
            if (b.StartsWith(a))
            {
                relativePath = relativePath.Substring(a.Length);
            }

            if (string.IsNullOrWhiteSpace(relativePath) != true)
            {
                var prefix = item.IsDirectoryExists ? "&gt; " : "";
                var linkText = $"<p>{preText}<a href=\"{relativePath}\">{prefix}{item.Title}</a></p>\n";
                sbIndex.Append(linkText);
            }
            if (item.Items.Count > 0)
            {
                foreach (WorkspaceTreeViewItem subItem in item.Items)
                {
                    BuildWorkspaceIndexFileLinkTexts(subItem, ref sbIndex);
                }
            }
        }

        /// <summary>
        /// 将字符串开头的数字去除。例如：1－2－3－xxx，格式化后只保留xxx。但如果只剩下空格，就返回原始值。
        /// </summary>
        private string FormatDocumentTitle(DirectoryInfo di)
        {
            return FormatDocumentTitle(di.Name);
        }

        /// <summary>
        /// 取指定 Html 文档中在“<Title></Title>”之中的标题文本。
        /// </summary>
        /// <param name="fileInfo">Html 文件的位置。</param>
        /// <returns>返回 Html 文件的标题。</returns>
        private string GetHtmlFileTile(FileInfo fileInfo)
        {
            if (fileInfo == null || fileInfo.Exists == false) return "";

            //< title > 2016年江苏省普通高等学校招生全国统一考试（江苏卷）（选修科目）说明 </ title >

            Regex regex = new Regex(@"(?<=(<title>)).*(?=(</title>))");

            var encoding = (miUtf8.IsChecked == true) ? Encoding.UTF8 : Encoding.GetEncoding("GB2312");

            using (StreamReader sr = new StreamReader(fileInfo.FullName, encoding))
            {
                var lineText = sr.ReadLine();
                while (lineText != null)
                {
                    var match = regex.Match(lineText);
                    if (match.Success)
                    {
                        return match.Value;
                    }

                    lineText = sr.ReadLine();
                }
            }

            return "";
        }

        /// <summary>
        /// 创建 Html 目录文件时，需要知道各目录之间的层级关系，此关系由前缀决定。
        /// </summary>
        /// <param name="preDirectory">前一目录路径。</param>
        /// <param name="isfolder">是否目录。</param>
        /// <returns>返回前缀字符串，表示层级关系。</returns>
        private string BuildPrefix(string preDirectory, bool isfolder)
        {
            if (string.IsNullOrEmpty(preDirectory)) return string.Empty;
            if (preDirectory == "/" || preDirectory == "\\") return string.Empty;

            StringBuilder sb = new StringBuilder();
            foreach (char c in preDirectory)
            {
                if (c == '/' || c == '\\') sb.Append("　");
            }

            if (isfolder)
            {
                var result = sb.ToString();
                if (preDirectory.EndsWith("/") || preDirectory.EndsWith("\\"))
                {
                    if (result.EndsWith("　")) return result.Substring(0, result.Length - 1);
                    else return result;
                }
                else return result;
            }
            else return sb.ToString();
        }

        /// <summary>
        /// 双击“历史导出目录”列表中某一项，将当前工作区向该项指向的目录导出编译的所有 Html 文件以及资源文件。
        /// </summary>
        void row_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            OutportFiles((sender as RecentDirectoryListBoxItem).DirectoryPath);
        }

        /// <summary>
        /// 编译所有指定的 Markdown 文件。
        /// </summary>
        /// <param name="compiledFiles">用于返回被编译的 Markdown 文件的信息，这样可用于生成目录 Html 文件。</param>
        public static bool CompileAllMdFiles(ref List<FileSystemEntry> compiledFiles)
        {
            //旧版，不好。
            //CopyCompiledDirectory(Globals.PathOfWorkspace, "", true, ref compiledFiles);

            if (Globals.MainWindow.tvWorkDirectory.Items.Count <= 0) return false;

            var rootItem = Globals.MainWindow.tvWorkDirectory.Items[0] as WorkspaceTreeViewItem; ;
            if (rootItem == null) return false;

            // 必须将“预览前总是编译选项”选中
            var shouldRestoreOption = false;
            if (Globals.MainWindow.AlwaysCompileBeforePreview == false)
            {
                shouldRestoreOption = true;
                Globals.MainWindow.MiAlwaysCompileBeforePreview_Click(null, null);
            }

            CopyCompiledDirectory(rootItem, "", true, ref compiledFiles);

            // 恢复“预览前总是编译”选项为选中状态
            if (shouldRestoreOption)
            {
                Globals.MainWindow.MiAlwaysCompileBeforePreview_Click(null, null);
            }

            return compiledFiles.Count > 0;
        }

        /// <summary>
        /// 递归获取指定工作区条目项下所有有效的 Markdown 文件路径（含目录元文件）。
        /// </summary>
        /// <param name="wtvi"></param>
        /// <param name="filePathList"></param>
        /// <returns></returns>
        private static void GetMarkdownFilePathsFromWorkspaceItem(WorkspaceTreeViewItem wtvi, ref List<string> filePathList)
        {
            if (wtvi == null || filePathList == null) return;

            if (wtvi.IsMarkdownFilePath)
            {
                if (Globals.MainWindow.IgnoreEncryptedFiles)
                {
                    //编译工作区时忽略已加密文件
                    string password, passwordTip;
                    var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(wtvi.FullPath, out password, out passwordTip);
                    if (isEncrypted) return;//忽略被加密的文档。
                }

                if (Globals.MainWindow.IgnoreAbortedFiles)
                {
                    //编译工作区时忽略被标记为“废弃”的文件。
                    var isAbortedFile = CustomMarkdownSupport.IsFileAborted(wtvi.FullPath);
                    if (isAbortedFile) return;
                }

                filePathList.Add(wtvi.FullPath);
            }
            else if (wtvi.IsMetaDirectoryPath)
            {
                var metaFilePath = wtvi.MetaFilePath;
                if (Globals.MainWindow.IgnoreEncryptedFiles)
                {
                    //编译工作区时忽略已加密文件
                    string password, passwordTip;
                    var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(metaFilePath, out password, out passwordTip);
                    if (isEncrypted) return;//忽略被加密的文档。
                }

                if (Globals.MainWindow.IgnoreAbortedFiles)
                {
                    //编译工作区时忽略被标记为“废弃”的文件。
                    var isAbortedFile = CustomMarkdownSupport.IsFileAborted(metaFilePath);
                    if (isAbortedFile) return;
                }

                filePathList.Add(wtvi.MetaFilePath);
                if (wtvi.Items.Count > 0)
                {
                    foreach (var subItem in wtvi.Items)
                    {
                        var subWtvi = subItem as WorkspaceTreeViewItem;
                        GetMarkdownFilePathsFromWorkspaceItem(subWtvi, ref filePathList);
                    }
                }
            }
        }

        /// <summary>
        /// 2017年7月23日，新版，按工作区管理器布局编译Html文件，不好。
        /// 编译指定目录下（含下级）所有 Markdown 文件，并将编译好的 Html 文件按目录结构输出到指定的另一个目录中。
        /// </summary>
        /// <param name="srcDirectory">Markdown 文件所在的源目录的路径。</param>
        /// <param name="destDirectory">传入无效的路径可以避免复制，这样就只是将 Markdown 文件编译为 Html 文件了。编译的 Html 文件与 Markdown 文件在同一目录下。</param>
        /// <param name="compileMarkdownFilesBeforeCopy">在向指定目录复制编译生成的 Html 文件之前是否先重新编译一下。</param>
        /// <param name="fileSystemEntries">此参数用于生成目录索引 Html 文件。</param>
        public static void CopyCompiledDirectory(WorkspaceTreeViewItem wtvi, string destDirectory, bool compileMarkdownFilesBeforeCopy, ref List<FileSystemEntry> fileSystemEntries)
        {
            if (string.IsNullOrEmpty(destDirectory) == false && destDirectory[destDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                destDirectory += Path.DirectorySeparatorChar;
            }

            List<string> filePathList = new List<string>();
            GetMarkdownFilePathsFromWorkspaceItem(wtvi, ref filePathList);

            if (compileMarkdownFilesBeforeCopy)
            {
                for (int i = 0; i < filePathList.Count; i++)
                {
                    string mdFilePath = filePathList[i];

                    //必须分两次循环，先考虑编译，再复制
                    if (File.Exists(mdFilePath))
                    {
                        var srcFilePath = mdFilePath;// srcDirectory + Path.GetFileName(fileEntry);
                        if (srcFilePath.ToLower().EndsWith(".md"))
                        {
                            string htmlTitle = "";

                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(srcFilePath, out password, out passwordTip);
                                if (isEncrypted) continue;//忽略被加密的文档。
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                var isAbortedFile = CustomMarkdownSupport.IsFileAborted(srcFilePath);
                            }

                            string previewPagePath = "";
                            string previewPageLink = "";
                            if (i >= 1)
                            {
                                previewPagePath = MarkdownEditorBase.GetRelativePath(srcFilePath, filePathList[i - 1], true);
                                previewPageLink = $"<a href=\"{previewPagePath}\" alt=\"←\">&lt; 前页</a>";
                            }

                            string nextPagePath = "";
                            string nextPageLink = "";
                            if (i < filePathList.Count - 1)
                            {
                                nextPagePath = MarkdownEditorBase.GetRelativePath(srcFilePath, filePathList[i + 1], true);
                                nextPageLink = $"<a href=\"{nextPagePath}\" alt=\"→\">后页 &gt;</a>";
                            }

                            var filePath = CustomMarkdownSupport.CompileOrGetHtmlFile(srcFilePath,
                                        Globals.MainWindow.HtmlHeadersCollapse,
                                        null, 1, ref htmlTitle, true, false, false, 1,
                                        previewPageLink, nextPageLink, previewPagePath, nextPagePath);
                            if (filePath == null) continue;
                        }
                    }
                }
            }

            foreach (string entry in filePathList)
            {
                if (Directory.Exists(entry))
                {
                    if (entry.EndsWith("~") == false && entry.EndsWith("~" + Path.DirectorySeparatorChar) == false)
                    {
                        fileSystemEntries.Add(new FileSystemEntry()
                        {
                            FileFullName = "directory_entry:///" + entry,
                            Title = "",
                        });
                    }

                    if (string.IsNullOrWhiteSpace(destDirectory))
                    {
                        CopyCompiledDirectory(entry, "", compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                    else
                    {
                        CopyCompiledDirectory(entry, destDirectory + Path.GetFileName(entry), compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                }
                else
                {
                    if (entry.ToLower().EndsWith(".md") && File.Exists(entry) &&
                        entry.ToLower().EndsWith("~.md") == false)
                    {
                        var fileInfo = new FileInfo(entry.Substring(0, entry.Length - 3) + ".html");
                        if (fileInfo.Name.StartsWith("_") == false)
                        {
                            fileSystemEntries.Add(
                                new FileSystemEntry()
                                {
                                    FileFullName = fileInfo.FullName,
                                    Title = fileInfo.Name,
                                });
                        }
                    }

                    var destFileFullPath = destDirectory + Path.GetFileName(entry);
                    if (string.IsNullOrWhiteSpace(destDirectory) == false)//为空字符串时不复制。
                    {
                        if (Directory.Exists(destDirectory) == false && string.IsNullOrWhiteSpace(destDirectory) == false)
                        {
                            try
                            {
                                Directory.CreateDirectory(destDirectory);

                                if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                                {
                                    System.IO.File.Copy(entry, destFileFullPath, true);
                                }
                            }
                            catch (Exception ex)
                            {
                                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                            }
                        }
                        else
                        {

                            if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                            {
                                System.IO.File.Copy(entry, destFileFullPath, true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 旧版，按工作区目录编译Html文件，不好。
        /// 编译指定目录下（含下级）所有 Markdown 文件，并将编译好的 Html 文件按目录结构输出到指定的另一个目录中。
        /// </summary>
        /// <param name="srcDirectory">Markdown 文件所在的源目录的路径。</param>
        /// <param name="destDirectory">传入无效的路径可以避免复制，这样就只是将 Markdown 文件编译为 Html 文件了。编译的 Html 文件与 Markdown 文件在同一目录下。</param>
        /// <param name="compileMarkdownFilesBeforeCopy">在向指定目录复制编译生成的 Html 文件之前是否先重新编译一下。</param>
        /// <param name="fileSystemEntries">此参数用于生成目录索引 Html 文件。</param>
        public static void CopyCompiledDirectory(string srcDirectory, string destDirectory, bool compileMarkdownFilesBeforeCopy, ref List<FileSystemEntry> fileSystemEntries)
        {
            String[] Entries;

            if (string.IsNullOrEmpty(destDirectory) == false && destDirectory[destDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                destDirectory += Path.DirectorySeparatorChar;
            }

            //不能用下面这段代码，因为destDirectory完全可能不是合法的目录路径，这样会引发异常。
            #region 废弃代码
            //if (string.IsNullOrEmpty(destDirectory) == false && !Directory.Exists(destDirectory))
            //{
            //    try
            //    {
            //        Directory.CreateDirectory(destDirectory);
            //    }
            //    catch (Exception ex)
            //    {
            //        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            //        return;
            //    }
            //} 
            #endregion

            Entries = Directory.GetFileSystemEntries(srcDirectory);

            DirectoryInfo di = new DirectoryInfo(srcDirectory);
            string mataFileEntry = null;
            var diName = (di.Name.EndsWith("\\") || di.Name.EndsWith("/")) ? di.Name.Substring(0, di.Name.Length - 1).ToLower() : di.Name.ToLower();
            List<string> entryList = new List<string>();

            for (int i = 0; i < Entries.Length; i++)
            {
                if (Entries[i].ToLower().EndsWith($"_{diName}.md"))
                {
                    mataFileEntry = Entries[i];
                }
                else
                {
                    entryList.Add(Entries[i]);
                }
            }

            entryList.Sort(new FileSystemEntriesCompare());

            if (mataFileEntry != null)
            {
                entryList.Insert(0, mataFileEntry);
            }

            if (compileMarkdownFilesBeforeCopy)
            {
                foreach (string fileEntry in entryList)
                {
                    //必须分两次循环，先考虑编译，再复制
                    if (File.Exists(fileEntry))
                    {
                        var srcFilePath = fileEntry;// srcDirectory + Path.GetFileName(fileEntry);
                        if (srcFilePath.ToLower().EndsWith(".md"))
                        {
                            string htmlTitle = "";

                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(srcFilePath, out password, out passwordTip);
                                if (isEncrypted) continue;//忽略被加密的文档。
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                var isAbortedFile = CustomMarkdownSupport.IsFileAborted(srcFilePath);
                            }

                            var filePath = CustomMarkdownSupport.CompileOrGetHtmlFile(srcFilePath,
                                        Globals.MainWindow.HtmlHeadersCollapse,
                                        null, 1, ref htmlTitle, true);
                            if (filePath == null) continue;
                        }
                    }
                }
            }

            foreach (string entry in entryList)
            {
                if (Directory.Exists(entry))
                {
                    if (entry.EndsWith("~") == false && entry.EndsWith("~" + Path.DirectorySeparatorChar) == false)
                    {
                        fileSystemEntries.Add(new FileSystemEntry()
                        {
                            FileFullName = "directory_entry:///" + entry,
                            Title = "",
                        });
                    }

                    if (string.IsNullOrWhiteSpace(destDirectory))
                    {
                        CopyCompiledDirectory(entry, "", compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                    else
                    {
                        var tempDirName = Path.GetFileName(entry);
                        if (tempDirName.StartsWith("_") && tempDirName.EndsWith("~") && Globals.MainWindow.ChmImportDirectoryPathes.Contains(tempDirName))
                        {
                            if (Directory.Exists(destDirectory + tempDirName))
                            {
                                var answer = LMessageBox.Show($"导出目标目录下已存在名为【{tempDirName}】的文件夹。" +
                                    $"如果您上次导出后没有修改过其中的资源文件，没有必要再次复制。\r\n\r\n　　要跳过此目录吗？",
                                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                                if (answer == MessageBoxResult.Yes)
                                    continue;  // _xxx~ 这样的外部库，除非被修改过，否则复制一次就可以了
                            }
                        }

                        CopyCompiledDirectory(entry, destDirectory + Path.GetFileName(entry), compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                }
                else
                {
                    var tmp = entry.ToLower();
                    if (tmp.EndsWith(".md")) continue;

                    if (File.Exists(entry))
                    {
                        if (tmp.EndsWith(".chm") == false &&
                            tmp.EndsWith(".hhc") == false &&
                            tmp.EndsWith(".hhk") == false &&
                            tmp.EndsWith(".hhp") == false &&
                            tmp.EndsWith(".editing") == false &&
                            tmp.EndsWith(".bak") == false &&
                            tmp.EndsWith("workspaceitems.xml") == false &&
                            tmp.EndsWith("configure.xml") == false &&
                            tmp.EndsWith("help~.html") == false)
                        {
                            var fileInfo = new FileInfo(entry.Substring(0, entry.Length - 3) + ".html");
                            //if (fileInfo.Name.StartsWith("_") == false)
                            //{
                            fileSystemEntries.Add(
                                new FileSystemEntry()
                                {
                                    FileFullName = fileInfo.FullName,
                                    Title = fileInfo.Name,
                                });
                            //}
                        }
                        else continue;   //这些文件不应该导出。
                    }

                    var destFileFullPath = destDirectory + Path.GetFileName(entry);
                    if (string.IsNullOrWhiteSpace(destDirectory) == false)//为空字符串时不复制。
                    {
                        if (Directory.Exists(destDirectory) == false && string.IsNullOrWhiteSpace(destDirectory) == false)
                        {
                            try
                            {
                                Directory.CreateDirectory(destDirectory);

                                if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                                {
                                    System.IO.File.Copy(entry, destFileFullPath, true);
                                }
                            }
                            catch (Exception ex)
                            {
                                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                            }
                        }
                        else
                        {

                            if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                            {
                                System.IO.File.Copy(entry, destFileFullPath, true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 编译当前活动编辑器，并预览之。
        /// </summary>
        private void btnCompileAndPreviewHtml_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 插入 Html 格式的水平线标记文本。
        /// </summary>
        private void miInsertHorizontalLine_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            if (mi == null) return;

            var value = int.Parse(mi.Tag as string);

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (value == 0)
            {
                eti.EditorBase.SelectedText = "\r\n\r\n----------------------------------------\r\n\r\n";
            }
            else
            {
                eti.EditorBase.SelectedText = "<hr class=\"hr" + value + "\"/>\r\n";
            }

            var destSel = eti.EditorBase.SelectionStart + eti.EditorBase.SelectionLength;
            if (destSel < eti.EditorBase.Document.TextLength)
            {
                eti.EditorBase.Select(destSel, 0);
            }
        }

        /// <summary>
        /// 用一对方括弧将选定文本片段括起来。形如：【原文本】。
        /// </summary>
        private void miWrapWithSquareQuoters_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.WrapTextWithSquareQuotes();
        }

        /// <summary>
        /// 插入一个选择题相关标记。
        /// </summary>
        private void miInsertChoiceQuestion_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.InsertChoiceQuestionTags();
        }

        /// <summary>
        /// 插入一个判断题相关标记。
        /// </summary>
        private void miInsertJudgeQuestion_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.InsertJudgeQuestionTags((sender as MenuItem).Tag.ToString());
        }

        /// <summary>
        /// 检验当前活动编辑器中的试题是否合法（不合法的是指格式不全，不能用于演示。）
        /// </summary>
        private void miValidateTestPaper_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            StringBuilder errorMsg = new StringBuilder();
            if (QuestionValidateManager.ValidateForPresentation(QuestionBuilder.OutLineExamsText(eti.EditorBase.Document.Text), errorMsg))
            {
                tbxValidatedInfo.Text = "文件通过合法性验证，可以使用。\r\n==========√√√√√==========\r\n";
            }
            else
            {
                tbxValidatedInfo.Text = "文件未通过合法性验证，错误消息如下：\r\n============××××××============\r\n" + errorMsg.ToString();
            }

            if (tcRightToolBar.SelectedItem != tabValidateDocument)
            {
                tcRightToolBar.SelectedItem = tabValidateDocument;
            }
        }

        /// <summary>
        /// 演示当前活动编辑器中所有试题。
        /// </summary>
        private void miPresentation_Click(object sender, RoutedEventArgs e)
        {
            PresentationExams();
        }

        /// <summary>
        /// 演示当前活动编辑器中的试题（如果有）。
        /// </summary>
        public void PresentationExams()
        {
            var edit = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (edit == null)
            {
                LMessageBox.Show("没有文档可以显示！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //if (edit.EditorBase.Text.Contains("｜") || edit.EditorBase.Text.Contains("|"))
            //{
            //    var answer = LMessageBox.Show("发现源文件中带有 ｜ 或 | 字符。这两个字符在 LME 中通常用以定义二维文字表。\r\n\r\n　　本功能目前在演示试题时尚不支持二维文字表。\r\n\r\n　　要继续吗？",
            //         Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
            //    if (answer != MessageBoxResult.Yes) return;
            //}

            var examsText = QuestionBuilder.OutLineExamsText(edit.EditorBase.Document.Text);

            StringBuilder errorMsg = new StringBuilder();
            if (QuestionValidateManager.ValidateForPresentation(examsText, errorMsg))//这包括了填空题。
            {
                tbxValidatedInfo.Text = "文件通过合法性验证，可以使用。\r\n========√√√√========\r\n";
            }
            else
            {
                tbxValidatedInfo.Text = "文件未通过合法性验证，错误消息如下：\r\n=========××××========\r\n" + errorMsg.ToString();

                if (tcRightToolBar.SelectedItem != tabValidateDocument)
                {
                    tcRightToolBar.SelectedItem = tabValidateDocument;
                }

                LMessageBox.Show("此文档中无试题或试题有格式错误，无法演示！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);

                return;
            }

            //这句不能少，否则头一次显示不出任何试题。
            edit.PresentationWindow.BuildQuestions(examsText);
            edit.PresentationWindow.ShowDialog();
        }

        /// <summary>
        /// Html 预览区 TabItem 标头双击编译当前活动编辑器并预览。
        /// </summary>
        private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                CompileAndPreviewHtml(true);
            }
        }

        /// <summary>
        /// Html 预览区 TabItem 标头变色效果。
        /// </summary>
        private void StackPanel_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as StackPanel).Background = Brushes.LightGray;
        }

        /// <summary>
        /// Html 预览区 TabItem 标头变色效果。
        /// </summary>
        private void StackPanel_MouseLeave(object sender, MouseEventArgs e)
        {
            (sender as StackPanel).Background = Brushes.Transparent;
        }

        /// <summary>
        /// 创建试题演示界面所需要的提问名册。演示试题时，按特定快捷键就可以弹出一个学生名字提问。
        /// </summary>
        private void miCreateStudentsList_Click(object sender, RoutedEventArgs e)
        {
            var inputBoxResult = InputBox.Show(Globals.AppName, "　　请输入班级名：", "", false);

            if (string.IsNullOrEmpty(inputBoxResult)) return;

            try
            {
                string studentListDirectoryPath = Globals.PathOfUserFolder + (Globals.PathOfUserFolder.EndsWith("\\") ? "" : "\\") + "StudentsLists\\";
                if (Directory.Exists(studentListDirectoryPath) == false)
                {
                    Directory.CreateDirectory(studentListDirectoryPath);
                }

                DirectoryInfo studentListDirectoryinfo = new DirectoryInfo(studentListDirectoryPath);

                var newStudentsListFilePath = studentListDirectoryPath + (inputBoxResult.EndsWith(".txt") ? inputBoxResult : (inputBoxResult + ".txt"));
                if (File.Exists(newStudentsListFilePath) == false)
                {
                    //或不存在同名文件，就创建一个。
                    File.WriteAllText(newStudentsListFilePath, Properties.Resources.SampleClassFileContent);
                }

                //编辑文件
                var sw = new PlainTextEditor(newStudentsListFilePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                    Title = Globals.AppName + " - 创建班级名册",
                };

                if (File.Exists(newStudentsListFilePath))
                {
                    sw.editorBase.Text = File.ReadAllText(newStudentsListFilePath);
                }

                if (string.IsNullOrEmpty(sw.editorBase.Text))
                {
                    sw.editorBase.Text = Properties.Resources.SampleClassFileContent;
                }

                if (sw.ShowDialog() == true)
                {
                    LMessageBox.Show("新的班级名册在重新启动后才会生效！", Globals.AppName, MessageBoxButton.OK,
                         MessageBoxImage.Information);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 切换“IsExamEnabled”开关。此开关打开时才能使用试题编辑、演示功能。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miIsExamEnabled_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsExamEnabled == false && this.IsAutoCompletionEnabled == false)
            {
                var result = LMessageBox.Show("需要打开【自动完成】，要继续吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question, "EnableAutoCompleteWithoutTip");
                if (result != MessageBoxResult.Yes) return;

                miIsAutoCompletionEnabled.IsChecked = true;
                this.IsAutoCompletionEnabled = true;
                App.ConfigManager.Set("IsAutoCompletionEnabled", true.ToString());
            }

            miIsExamEnabled.IsChecked = !miIsExamEnabled.IsChecked;
            this.IsExamEnabled = miIsExamEnabled.IsChecked;
            App.ConfigManager.Set("IsExamEnabled", miIsExamEnabled.IsChecked.ToString());
        }

        /// <summary>
        /// 切换“IsAutoCompletionEnabled”属性的开关。此开关打开时才能使用“自动完成”功能。
        /// </summary>
        private void miIsAutoCompletionEnabled_Clicked(object sender, RoutedEventArgs e)
        {
            miIsAutoCompletionEnabled.IsChecked = !miIsAutoCompletionEnabled.IsChecked;
            this.IsAutoCompletionEnabled = miIsAutoCompletionEnabled.IsChecked;
            App.ConfigManager.Set("IsAutoCompletionEnabled", miIsAutoCompletionEnabled.IsChecked.ToString());

            if (this.IsAutoCompletionEnabled == false)
            {
                if (this.IsExamEnabled)
                {
                    this.miIsExamEnabled.IsChecked = this.IsExamEnabled = false;
                    App.ConfigManager.Set("IsExamEnabled", false.ToString());
                }

                if (this.IsEnToChineseDictEnabled)
                {
                    this.miIsEnToChineseDictEnabled.IsChecked = this.IsEnToChineseDictEnabled = false;
                    App.ConfigManager.Set("IsEnToChineseDictEnabled", false.ToString());
                }
            }
        }

        /// <summary>
        /// 双击左下角图像预览区，选定当前预览的图像文件在工作区管理器中的对应条目。
        /// </summary>
        private void imagePreviewOutBorder_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                var wti = (sender as Border).Tag as WorkspaceTreeViewItem;
                if (wti == null) return;

                wti.IsSelected = true;
            }
        }

        /// <summary>
        /// 根据“查找范围”框的限制，在指定范围的 Markdown 文件中查找所有的“锚”。
        /// </summary>
        private void btnFindAnchors_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            //锚的定义方式：
            //[锚名](@锚ID)
            //锚名可以省略，但锚ID不能省略。
            //编译后，会变成这样：
            //<a id="锚ID">锚名</a>
            string keyWord = @"(\[.*\]\(@.*\))|([\(（][ 　\t]*?[@][ 　\t]*?[a-zA-Z][a-zA-Z0-9\-_]*?[ 　\t]*?[\)）])";

            FindText(keyWord, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), true, tvFindAndReplace);

            if (tcRightToolBar.SelectedItem != tiFindResult)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 为方便起个别名。
        /// </summary>
        public MarkdownEditor ActiveEditor { get { return ActivedEditor; } }

        /// <summary>
        /// 返回当前活动的 Markdown 编辑器。
        /// </summary>
        public MarkdownEditor ActivedEditor
        {
            get
            {
                if (this.mainTabControl.Items.Count <= 0) return null;

                return this.mainTabControl.SelectedItem as MarkdownEditor;
            }
        }

        public List<MarkdownEditor> Editors
        {
            get
            {
                if (this.mainTabControl.Items.Count <= 0) return null;
                var list = new List<MarkdownEditor>();

                foreach (var fe in this.mainTabControl.Items)
                {
                    var me = fe as MarkdownEditor;
                    if (me != null) list.Add(me);
                }
                return list;
            }
        }

        /// <summary>
        /// 启动时折叠左工具栏。
        /// </summary>
        public bool StartCollapseLeftTools { get; private set; }

        /// <summary>
        /// 启动时折叠右工具栏。
        /// </summary>
        public bool StartCollapseRightTools { get; private set; }

        /// <summary>
        /// 启动时显示工作区管理器。
        /// </summary>
        public bool SelectWorkspaceAtStart { get; private set; } = false;

        /// <summary>
        /// 在当前文档中查找对工作区管理器中选定条目的引用。
        /// </summary>
        private void miFindRefInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            FindRef("ActiveDocument");
        }

        /// <summary>
        /// 根据“查找范围”查找所有对当前工作区管理器中选定条目的引用。
        /// </summary>
        /// <param name="findArea">指定查找的范围，包括：“ActiveDocument”、“OpenedDocuments”、“AllFiles”之一。</param>
        private void FindRef(string findArea)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null) return;

            var shortName = wtvi.ShortName;
            if (shortName.ToLower().EndsWith(".md"))
            {
                shortName = shortName.Substring(0, shortName.Length - 3);
            }

            if (string.IsNullOrEmpty(shortName)) return;

            FindText(shortName, findArea, false, tvFindAndReplace);

            if (cdRightToolsArea.ActualWidth < 100)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
                SwitchRightToolBarToggle();
            }
        }

        /// <summary>
        /// 查找所有打开的文档中对当前条目的引用。
        /// </summary>
        private void miFindRefInOpenedDocument_Click(object sender, RoutedEventArgs e)
        {
            FindRef("OpenedDocuments");
        }

        /// <summary>
        /// 按工作区管理器中选定条目的名称查找工作区（及其下级目录）中所有 Markdown 文件中对此文件（或目录）的引用。
        /// 这通常用于改变目录（或文件）的名称前，以防止这些引用失效。
        /// 一般情况下，不应该更改目录（或文件）的名称，以防止引用失效。
        /// 如果有排序的需要，应该在创建时给文件（或目录）赋予数字开头的名称，且数字不应连贯。
        /// </summary>
        private void miFindRefInDiskFiles_Click(object sender, RoutedEventArgs e)
        {
            bool someFileIsModified = false;
            foreach (var i in this.mainTabControl.Items)
            {
                MarkdownEditor me = i as MarkdownEditor;
                if (me == null) continue;

                if (me.IsModified)
                {
                    someFileIsModified = true;
                    break;
                }
            }

            if (someFileIsModified)
            {
                var result = LMessageBox.Show("这个功能只管从磁盘上查找，而不管当前正在编辑的内容，所以查找的结果未必准确。\r\n" +
                    "　　如果要保证查找结果准确，请先保存这些文档或者使用“查找打开的文档”、“查找当前文档”这两个菜单条目！\r\n\r\n　　要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;
            }

            FindRef("AllFiles");
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加倾斜标记（不能跨行）。
        /// </summary>
        private void miItalic_Click(object sender, RoutedEventArgs e)
        {
            WrapWithItalic();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加加粗标记（不能跨行）。
        /// </summary>
        private void miBold_Click(object sender, RoutedEventArgs e)
        {
            WrapWithBold();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加删除线标记（不能跨行）。
        /// </summary>
        private void miUnderLine_Click(object sender, RoutedEventArgs e)
        {
            WrapWithUTag();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加下划线标记（不能跨行）。
        /// </summary>
        private void miStrikeLine_Click(object sender, RoutedEventArgs e)
        {
            WrapWithSTag();
        }

        private void miSelectWorkspaceAtStart_Click(object sender, RoutedEventArgs e)
        {
            miSelectWorkspaceAtStart.IsChecked = !miSelectWorkspaceAtStart.IsChecked;
            SelectWorkspaceAtStart = miSelectWorkspaceAtStart.IsChecked;
            App.ConfigManager.Set("SelectWorkspaceAtStart", miSelectWorkspaceAtStart.IsChecked.ToString());
        }

        /// <summary>
        /// 切换全屏状态。
        /// </summary>
        private void miFullScreen_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPreview;
        }

        private void miFullScreenEditArea_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenEditing;
        }

        /// <summary>
        /// 全屏预览。
        /// </summary>
        private void miPreviewFullScreen_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.FullScreenPreview;
        }

        /// <summary>
        /// 全屏。
        /// </summary>
        private void btnFullScreen_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPreview;
        }

        /// <summary>
        /// 旧版的自定义最小化窗口按钮。
        /// </summary>
        private void btnMinize_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }

        /// <summary>
        /// 旧版的自定义恢复窗口按钮。
        /// </summary>
        private void btnRestore_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.WindowState == WindowState.Normal)
            {
                this.WindowState = WindowState.Maximized;
            }
            else if (this.WindowState == WindowState.Maximized)
            {
                this.WindowState = WindowState.Normal;
            }
        }

        /// <summary>
        /// 旧版的自定义关闭窗口按钮。
        /// </summary>
        private void btnClose_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// 旧版的自定义窗口最大化、最小化、恢复图标。
        /// </summary>
        private BitmapImage restoreImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/restore.png"));
        /// <summary>
        /// 旧版的自定义窗口最大化、最小化、恢复图标。
        /// </summary>
        private BitmapImage maxsizeImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/maxsize.png"));

        /// <summary>
        /// [用于调试]输出指定控件的模板文本。仅用于调试。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miWriteControlTemplate_Click(object sender, RoutedEventArgs e)
        {
            string templateText = GetTemplateXamlCode(btnSearchInWorkspace as Control);
            File.WriteAllText("D:\\控件模板.txt", templateText);
            LMessageBox.Show("控件模板文件已输出到：D:\\控件模板.txt");
        }

        public event EventHandler<HtmlCompileChangedEventArgs> HtmlOptionChanged;

        protected void OnHtmlOptionChanged(object sender, HtmlCompileChangedEventArgs e)
        {
            if (HtmlOptionChanged != null)
                HtmlOptionChanged(sender, e);
        }

        public class HtmlCompileChangedEventArgs
        {
            public string HtmlOptionID { get; set; }
            public string HtmlOptionText { get; set; }
        }

        /// <summary>
        /// [用于调试]输出指定控件的模板文本。仅用于调试。
        /// </summary>
        /// <param name="ctrl"></param>
        /// <returns></returns>
        string GetTemplateXamlCode(Control ctrl)
        {
            FrameworkTemplate template = ctrl.Template;
            string xaml = "";
            if (template != null)
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.IndentChars = new string(' ', 4);
                settings.NewLineOnAttributes = true;
                StringBuilder strbuild = new StringBuilder();
                XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
                try
                {
                    XamlWriter.Save(template, xmlwrite);
                    xaml = strbuild.ToString();
                }
                catch (Exception exc)
                {
                    xaml = exc.Message;
                }
            }
            else
            {
                xaml = "no template";
            }
            return xaml;
        }

        string GetStyleXamlCode(Control ctrl)
        {
            var style = ctrl.Style;
            string xaml = "";
            if (style != null)
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.IndentChars = new string(' ', 4);
                settings.NewLineOnAttributes = true;
                StringBuilder strbuild = new StringBuilder();
                XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
                try
                {
                    XamlWriter.Save(style, xmlwrite);
                    xaml = strbuild.ToString();
                }
                catch (Exception exc)
                {
                    xaml = exc.Message;
                }
            }
            else
            {
                xaml = "no template";
            }
            return xaml;
        }

        /// <summary>
        /// 旧的自定义主窗口标题栏，已无必要存在。
        /// </summary>
        private void dpTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.DragMove();
            }

            if (e.ClickCount == 2)
            {
                if (this.WindowState == WindowState.Maximized)
                {
                    this.WindowState = WindowState.Normal;
                }
                else if (this.WindowState == WindowState.Normal)
                {
                    this.WindowState = WindowState.Maximized;
                }
            }
        }

        private string defaultEncoding = "utf-8";
        /// <summary>
        /// 下次编译 html 时使用的默认编码。
        /// </summary>
        public string DefaultEncoding
        {
            get
            {
                return defaultEncoding;
            }
        }

        private string defaultIeVersion = "8";
        /// <summary>
        /// 下次编译 html 时指定使用的 IE 兼容版本号。
        /// </summary>
        public string DefaultIeVersion
        {
            get
            {
                return defaultIeVersion;
            }
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 UTF-8 编码。
        /// </summary>
        private void miUtf8_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miUtf8.Tag.ToString(), sender as MenuItem);
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 GB2312 编码。
        /// </summary>
        private void miGb2312_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miGb2312.Tag.ToString(), sender as MenuItem);
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 GB18030 编码。
        /// </summary>
        private void miGb18030_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miGb18030.Tag.ToString(), sender as MenuItem);
        }

        /// <summary>
        /// 选取（设置）下一次编译 Html 文档时使用什么编码方式。
        /// </summary>
        /// <param name="encoding">目前只支持 GB2312 和 UTF-8 以及 GB18030 这三种编码。</param>
        private void SelectEncoding(string encoding, MenuItem sender)
        {
            RefreshEncoding(encoding);

            App.WorkspaceConfigManager.Set("Encoding", encoding);

            OnHtmlOptionChanged(sender, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "Encoding",
                HtmlOptionText = encoding,
            });
        }

        /// <summary>
        /// 根据“encoding”的值来刷新主界面上关于编译 Html 时使用的编码方式的几个菜单的选取状态。
        /// </summary>
        /// <param name="encoding"></param>
        private void RefreshEncoding(string encoding)
        {
            if (string.IsNullOrEmpty(encoding))
            {
                encoding = "utf-8";
            }

            if (encoding == "gb2312")
            {
                defaultEncoding = "gb2312";
                miUtf8.IsChecked = false;
                miGb18030.IsChecked = false;
                miGb2312.IsChecked = true;
            }
            else if (encoding == "gb18030")
            {
                defaultEncoding = "gb18030";
                miUtf8.IsChecked = false;
                miGb2312.IsChecked = false;
                miGb18030.IsChecked = true;
            }
            else//都强制为UTF-8
            {
                if (encoding != "utf-8")
                {
                    encoding = "utf-8";
                }

                defaultEncoding = "utf-8";
                miUtf8.IsChecked = true;
                miGb2312.IsChecked = false;
                miGb18030.IsChecked = false;
            }
        }

        /// <summary>
        /// 为当前工作区创建一个 CHM 工程文件（包括对应的目录文件、索引文件）。
        /// </summary>
        private void miCreateCHMProject_Click(object sender, RoutedEventArgs e)
        {
            CreateChmProjectFiles(ChmCompileRange.WholeWorkSpace, onlyCreateChmProjectFile: true);
        }

        /// <summary>
        /// 2017年7月20日：
        /// 今天发现两个新 Bug: “忽略已加密文件”和“忽略被标记为废弃的文件”这两个选项无效。
        /// 继而想到，既然已经可以调整工作区管理器中条目的位置、层级，那么再让用户手动“创建 CHM 工程”就没有什么必要了。
        /// 干脆每次编译前自动重新创建工程文件好了！！！
        /// 所以将旧的方法单独提取出来重新修改成了下面这个方法。
        /// 
        /// 2019年4月11日：
        /// 为提供更精确的编译控制，还是开放了手动创建工程和编译功能。
        /// </summary>
        private bool CreateChmProjectFiles(ChmCompileRange range, bool onlyCreateChmProjectFile = false)
        {
            if (onlyCreateChmProjectFile == false)
            {
                if (SaveModifiedDocuments() == false) return false;
            }

            try
            {
                var suffix = "";
                if (range == ChmCompileRange.SelectedBranch)
                    suffix = "_";
                var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
                var chmProjectFilePath = prefixOfFiles + $"{suffix}.hhp";
                var chmContentFilePath = prefixOfFiles + $"{suffix}.hhc";
                var chmIndexFilePath = prefixOfFiles + $"{suffix}.hhk";

                //保证创建的工程文件是与当前工作区条目状态一致的。
                WorkspaceManager.SaveWorkspaceTreeviewToXml();

                if (onlyCreateChmProjectFile == false)
                {
                    //先编译所有Markdown文档。
                    if (range == ChmCompileRange.WholeWorkSpace)
                    {
                        var result2 = LMessageBox.Show("在创建 CHM 工程文件之前，应按 GB18030 编码方式对工作区中所有 Markdown 文件进行编译，要自动进行这步操作吗？",
                            Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                        if (result2 == MessageBoxResult.Yes)
                        {
                            miGb18030.IsChecked = true;
                            miGb18030_Click(null, null);

                            CompileWholeWorkspace();
                        }
                    }
                }

                var gb18030 = Encoding.GetEncoding("gb18030");

                //第①步，生成hhp工程文件
                var sbProject = new StringBuilder();

                #region 工程文件内容

                sbProject.Append("[OPTIONS]" + "\r\n");
                sbProject.Append("Compatibility=1.1 or later" + "\r\n");
                sbProject.Append("Compiled file=" + Globals.WorkspaceShortName + $"{suffix}.chm\r\n");
                sbProject.Append("Contents file=" + Globals.WorkspaceShortName + $"{suffix}.hhc\r\n");
                sbProject.Append("Default Font=新宋体,11,1\r\n");
                sbProject.Append("Default Window=LunarWin\r\n");

                //这里还需要进一步润色。
                var defPagePath = "";
                switch (range)
                {
                    case ChmCompileRange.SelectedBranch:
                        var defPageItem = (tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem);
                        switch (defPageItem.ItemType)
                        {
                            case WorkspaceTreeViewItem.Type.File:
                                defPagePath = defPageItem.HtmlFullPath.Substring(Globals.PathOfWorkspace.Length);
                                break;
                            default:
                                defPagePath = GetMetaFilePathOfDirectory(defPageItem.FullPath).Substring(Globals.PathOfWorkspace.Length);
                                break;
                        }
                        if (defPagePath.ToLower().EndsWith(".md"))
                            defPagePath = defPagePath.Substring(0, defPagePath.Length - 3) + ".html";
                        sbProject.Append($"Default topic={defPagePath}\r\n");
                        break;
                    default:
                        {
                            defPagePath = "_Index.html";
                            var fstHomePageItem = FindFirstHomePageItem();
                            if (fstHomePageItem != null)
                            {
                                switch (fstHomePageItem.ItemType)
                                {
                                    case WorkspaceTreeViewItem.Type.File:
                                        {
                                            defPagePath = fstHomePageItem.HtmlFullPath.Substring(Globals.PathOfWorkspace.Length);
                                            break;
                                        }
                                    case WorkspaceTreeViewItem.Type.MetaFile:
                                    case WorkspaceTreeViewItem.Type.Folder:
                                        {
                                            defPagePath = fstHomePageItem.MetaHtmlFilePath.Substring(Globals.PathOfWorkspace.Length);
                                            break;
                                        }
                                }
                            }
                            sbProject.Append($"Default topic={defPagePath}\r\n");
                            break;
                        }
                }

                sbProject.Append("Display compile progress=Yes" + "\r\n");
                sbProject.Append("Full-text search=Yes" + "\r\n");
                sbProject.Append("Index file=" + Globals.WorkspaceShortName + $"{suffix}.hhk\r\n");
                sbProject.Append("Language=0x804 中文(简体，中国)" + "\r\n");

                var title = "";
                switch (range)
                {
                    case ChmCompileRange.SelectedBranch:
                        {
                            if (tvWorkDirectory.SelectedItem == null)
                            {
                                title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);
                                break;
                            }

                            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
                            if (wtvi == null)
                            {
                                title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);
                                break;
                            }

                            title = wtvi.Title;
                            break;
                        }
                    default:
                        {
                            title = GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace);
                            if (string.IsNullOrEmpty(title) == false)
                            {
                                title = title.Trim(new char[] { '\r', '\n', ' ', '　', '\t', });
                            }

                            if (string.IsNullOrWhiteSpace(title))
                            {
                                title = Globals.WorkspaceShortName;
                            }
                            break;
                        }
                }

                sbProject.Append("Title=" + title);
                sbProject.Append("\r\n\r\n");
                sbProject.Append("[WINDOWS]\r\n");
                var chmConfig = "0x23520";
                if (Globals.MainWindow.ChmNavBarVisible != "True")
                {
                    chmConfig = "0x23500";  // 隐藏 CHM 左侧导航栏，但仍然会生成。适合用于使用 IE9 以上内核兼容模式时减少 HH.exe 界面的不协调感。
                }
                if (range == ChmCompileRange.WholeWorkSpace)
                {
                    sbProject.Append($"LunarWin=\"{title}\",\"{Globals.WorkspaceShortName + ".hhc"}\",\"{Globals.WorkspaceShortName + ".hhk"}\",\"{defPagePath}\",\"{defPagePath}\",\"_Index.html\",\"回目录\",,," + chmConfig + ",,0x4306e,,,,,,,,0\r\n");
                }
                else
                {
                    sbProject.Append($"LunarWin=\"{title}\",\"{Globals.WorkspaceShortName + suffix + ".hhc"}\",\"{Globals.WorkspaceShortName + suffix + ".hhk"}\",\"{defPagePath}\",,,,,," + chmConfig + ",,0x302e,,,,,,,,0\r\n");
                }
                sbProject.Append("\r\n\r\n");

                sbProject.Append("[FILES]" + "\r\n");

                if (range == ChmCompileRange.WholeWorkSpace)
                {
                    sbProject.Append("_Index.html\r\n");
                }

                var imgDirectoryPath = Globals.PathOfWorkspace + "Images~";
                if (Directory.Exists(imgDirectoryPath))
                {
                    var imgFiles = new DirectoryInfo(imgDirectoryPath).EnumerateFiles();
                    foreach (var imgFile in imgFiles)
                    {
                        var lowerName = imgFile.Name.ToLower();
                        if (lowerName.EndsWith(".png") || lowerName.EndsWith(".jpg") ||
                            lowerName.EndsWith(".jpeg") || lowerName.EndsWith(".gif") ||
                            lowerName.EndsWith(".bmp") || lowerName.EndsWith(".ico"))
                        {
                            sbProject.Append($"Images~\\{imgFile.Name}\r\n");
                        }
                    }
                }
                //else  //已无必要
                //{
                //    sbProject.Append("Images~\\header_icon_dark.png\r\n");
                //    sbProject.Append("Images~\\header_icon_light.png\r\n");
                //    sbProject.Append("Images~\\comment_dark.png\r\n");
                //    sbProject.Append("Images~\\comment_light.png\r\n");
                //    sbProject.Append("Images~\\region_e_dark.png\r\n");
                //    sbProject.Append("Images~\\region_e_light.png\r\n");
                //    sbProject.Append("Images~\\region_i_dark.png\r\n");
                //    sbProject.Append("Images~\\region_i_light.png\r\n");
                //    sbProject.Append("Images~\\region_q_dark.png\r\n");
                //    sbProject.Append("Images~\\region_q_light.png\r\n");
                //    sbProject.Append("Images~\\region_w_dark.png\r\n");
                //    sbProject.Append("Images~\\region_w_light.png\r\n");
                //    sbProject.Append("Images~\\menu_light.png\r\n");
                //    sbProject.Append("Images~\\menu_dark.png\r\n");

                //    sbProject.Append("Images~\\theme_switcher.png\r\n");
                //}
                sbProject.Append("jquery-1.7.0.min.js\r\n");
                sbProject.Append("menu_dark.js\r\n");
                sbProject.Append("menu_light.js\r\n");

                sbProject.Append("lesson_dark.css\r\n");
                sbProject.Append("lesson_light.css\r\n");
                sbProject.Append("menu_dark.css\r\n");
                sbProject.Append("menu_light.css\r\n");
                sbProject.Append("presentation_dark.css\r\n");
                sbProject.Append("presentation_light.css\r\n");

                CreateCustomCssFile("custom_lesson_dark.css");
                sbProject.Append("custom_lesson_dark.css\r\n");

                CreateCustomCssFile("custom_lesson_light.css");
                sbProject.Append("custom_lesson_light.css\r\n");

                CreateCustomCssFile("custom_menu_dark.css");
                sbProject.Append("custom_menu_dark.css\r\n");

                CreateCustomCssFile("custom_menu_light.css");
                sbProject.Append("custom_menu_light.css\r\n");

                CreateCustomCssFile("custom_presentation_dark.css");
                sbProject.Append("custom_presentation_dark.css\r\n");

                CreateCustomCssFile("custom_presentation_light.css");
                sbProject.Append("custom_presentation_light.css\r\n");

                //引用工作区目录内的资源文件（mp3、mp4）
                //为什么图像文件不需要——因为CHM编译器会自动嵌入图像文件。
                ImportResources(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, ref sbProject);

                //引入用户指定的外部文件
                ImportOuterFiles(ref sbProject);

                sbProject.Append("\r\n");

                sbProject.Append("[INFOTYPES]" + "\r\n");

                #endregion

                File.WriteAllText(chmProjectFilePath, sbProject.ToString(), gb18030);

                //第②步，生成hhc目录文件
                var sbContent = new StringBuilder();

                #region 目录文件内容

                //目录文件头，包括首页（_Index.html）
                sbContent.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
                sbContent.Append("<HTML>\r\n");
                sbContent.Append("<HEAD>\r\n");
                sbContent.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">\r\n");
                sbContent.Append("<!--Sitemap 1.0-->\r\n");
                sbContent.Append("</HEAD><BODY>\r\n");
                sbContent.Append("<OBJECT type=\"text/site properties\">\r\n");
                sbContent.Append("\t<param name=\"Window Styles\" value=\"0x800627\">\r\n");
                if (string.IsNullOrWhiteSpace(ChmImageType) == false)
                {
                    sbContent.Append($"\t<param name=\"ImageType\" value=\"{ChmImageType}\">\r\n");
                }
                sbContent.Append("</OBJECT>");

                //目录文件内容
                //如果是子目录，在子目录“<UL>”Tag前加上：
                //<LI> <OBJECT type="text/sitemap">
                //    < param name = "Name" value = "其它" >
                //    </ OBJECT >
                //有一层子目录就加一层“<UL>”Tag对

                List<string> invalidatedFilePaths;

                if (WorkspaceManager.WorkspaceItemsDocument != null && WorkspaceManager.WorkspaceItemsDocument.DocumentElement != null)
                {
                    sbContent.Append("<UL>\r\n");
                    switch (range)
                    {
                        case ChmCompileRange.SelectedBranch:
                            break;
                        default:
                            sbContent.Append("<LI> <OBJECT type = \"text/sitemap\">\r\n");
                            sbContent.Append("\t<param name =\"Name\" value=\"目录\">\r\n");
                            sbContent.Append("\t<param name =\"Local\" value=\"_index.html\">\r\n");
                            sbContent.Append("\t</OBJECT>\r\n");
                            break;
                    }

                    //这种是新式的，根据工作区管理器中各条目的布局来创建 CHM 目录文件。
                    List<string> encryptPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    switch (range)
                    {
                        case ChmCompileRange.SelectedBranch:
                            invalidatedFilePaths = CreateCHMContentFile(tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem, "", sbContent, ref encryptPaths, ref abortedPaths);
                            break;
                        default:
                            invalidatedFilePaths = CreateCHMContentFile(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, "", sbContent, ref encryptPaths, ref abortedPaths);
                            break;
                    }
                }
                else
                {
                    //这种是旧式的，根据目录层级创建 CHM 目录文件。
                    List<string> encryptPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    invalidatedFilePaths = CreateCHMContentFile(new DirectoryInfo(Globals.PathOfWorkspace), "", "", sbContent, ref encryptPaths, ref abortedPaths);
                }

                //目录文件尾
                sbContent.Append("</BODY></HTML>\r\n");

                #endregion

                File.WriteAllText(chmContentFilePath, sbContent.ToString(), gb18030);

                //第③步，生成hhk索引文件
                var sbIndex = new StringBuilder();

                #region 索引文件内容

                //索引文件头，包括首页（_Index.html）
                sbIndex.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
                sbIndex.Append("<HTML>\r\n");
                sbIndex.Append("<HEAD>\r\n");
                sbIndex.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">\r\n");
                sbIndex.Append("<!--Sitemap 1.0-->\r\n");
                sbIndex.Append("</HEAD><BODY>\r\n");
                sbIndex.Append("<OBJECT type=\"text /site properties\">\r\n");
                sbIndex.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
                sbIndex.Append("</OBJECT>\r\n");
                sbIndex.Append("<UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。

                //索引文件内容
                //如果是子目录，在子目录“< UL >”Tag前加上：
                //<LI><OBJECT type="text/sitemap">
                //    <param name = "Name" value = "其它">
                //    </OBJECT >
                //有一层子目录就加一层“<UL>”Tag对

                if (WorkspaceManager.WorkspaceItemsDocument != null && WorkspaceManager.WorkspaceItemsDocument.DocumentElement != null)
                {
                    switch (range)
                    {
                        case ChmCompileRange.SelectedBranch:
                            break;
                        default:
                            sbIndex.Append("\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                            sbIndex.Append("\t\t<param name =\"Name\" value=\"目录\">\r\n");
                            sbIndex.Append("\t\t<param name =\"Local\" value=\"_index.html\">\r\n");
                            sbIndex.Append("\t\t</OBJECT>\r\n");
                            break;
                    }

                    //这种是新式的，按照工作区管理器中条目的布局来创建索引文件。
                    List<string> encryptdPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    switch (range)
                    {
                        case ChmCompileRange.SelectedBranch:
                            CreateCHMIndexFile(tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem, sbIndex, ref encryptdPaths, ref abortedPaths);
                            break;
                        default:
                            CreateCHMIndexFile(WorkspaceManager.WorkspaceItemsDocument.DocumentElement as XmlNode, sbIndex, ref encryptdPaths, ref abortedPaths);
                            break;
                    }
                }
                else
                {
                    //这种是旧式的，直接按目录来生成索引文件。
                    List<string> encryptdPaths = new List<string>();
                    List<string> abortedPaths = new List<string>();
                    CreateCHMIndexFile(new DirectoryInfo(Globals.PathOfWorkspace), "", sbIndex, ref encryptdPaths, ref abortedPaths);
                }

                //索引文件尾
                sbIndex.Append("</UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。
                sbIndex.Append("</BODY></HTML>\r\n");

                #endregion

                File.WriteAllText(chmIndexFilePath, sbIndex.ToString(), gb18030);

                //第④步，查找 HHW 的安装位置，准备调用hhc.exe编译
                if (invalidatedFilePaths.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (string ifp in invalidatedFilePaths)
                    {
                        sb.Append("　　" + ifp + "\r\n");
                    }

                    LMessageBox.Show("创建 CHM 工程文件的过程中发现问题。这可能是由下列原因造成的：\r\n" +
                        "　　1. 指定的工作区目录中下列文件的物理路径中含有特殊字符，会导致编译的 CHM 文件中的链接失效；\r\n" +
                        "　　　 （如：空格、/、\\、*、\"、<、>、|、[、]、:、;、+、?、%、#、&、=等均不应使用。）\r\n" +
                        "　　2. 某些 Markdown 文件中可能没有实质性内容（或未能解密），导致无法编译为 Html 文件。\r\n\r\n" +
                        "　　为安全起见，请注意检查下列文件是否存在上列问题：\r\n\r\n" +
                         sb.ToString() + "\r\n\r\n" +
                        "　　★ 最容易导致错误的情况是工作区目录中带空格或中文字符！！！",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    //return false;  // 还是让用户作主吧。
                }

                if (onlyCreateChmProjectFile)
                {
                    LMessageBox.Show("已创建 CHM 工程文件。您可以使用微软公司的 Html Help Workshop 来进行修改或编译 CHM 文件。");
                }
                else
                {
                    //寻找 Html Help Workshop 的安装位置。
                    LoadHHWInstalledPath();

                    if (File.Exists(this.HHWInstalledPath) == false)
                    {
                        LMessageBox.Show("未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
                            "　　您可能需要到微软公司官方网站下载该工具并安装。",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                        return false;
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                return false;
            }
        }

        private void ImportResources(WorkspaceTreeViewItem wtvi, ref StringBuilder sbProject)
        {
            if (wtvi == null || sbProject == null) return;

            var path = wtvi.FullPath.ToLower();
            if (path.EndsWith(".mp3") || path.EndsWith(".mp4"))
            {
                sbProject.Append(wtvi.RelativePath + "\r\n");
            }

            foreach (var subItem in wtvi.Items)
            {
                ImportResources(subItem as WorkspaceTreeViewItem, ref sbProject);
            }
        }

        /// <summary>
        /// 向 CHM 工程文件中添加对用户指定的外部文件的引用。
        /// </summary>
        /// <param name="sbProject"></param>
        private void ImportOuterFiles(ref StringBuilder sbProject)
        {
            if (string.IsNullOrWhiteSpace(ChmImportDirectoryPathes)) return;

            try
            {
                var relativePathes = ChmImportDirectoryPathes.Split(new char[] { ';', }, StringSplitOptions.RemoveEmptyEntries);
                var wdi = new DirectoryInfo(Globals.PathOfWorkspace);

                var subDirectories = wdi.GetDirectories("_*~", SearchOption.TopDirectoryOnly);
                foreach (var subDi in subDirectories)
                {
                    bool isSelected = false;
                    foreach (var relativePath in relativePathes)
                    {
                        if (subDi.Name == relativePath)
                        {
                            isSelected = true;
                            break;
                        }
                    }

                    if (isSelected) ImportOuterFiles(subDi, ref sbProject);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 递归引入外部文件夹中所有文件。
        /// </summary>
        /// <param name="di"></param>
        /// <param name="sbProject"></param>
        private void ImportOuterFiles(DirectoryInfo di, ref StringBuilder sbProject)
        {
            var files = di.GetFiles();
            var pathOfWorkspace = Globals.PathOfWorkspace.ToLower();
            var pathOfWorkspaceLength = pathOfWorkspace.Length;
            foreach (var file in files)
            {
                if (file.FullName.ToLower().StartsWith(pathOfWorkspace))
                {
                    sbProject.Append(file.FullName.Substring(pathOfWorkspaceLength));
                    sbProject.Append("\r\n");
                }
            }

            var subDirs = di.GetDirectories();
            foreach (var subDi in subDirs)
            {
                ImportOuterFiles(subDi, ref sbProject);
            }
        }

        private static void CreateCustomCssFile(string fileName)
        {
            var customLessonDarkCssPath = Globals.PathOfWorkspace + fileName;
            if (File.Exists(customLessonDarkCssPath) == false)
            {
                using (var fsCLD = new FileStream(customLessonDarkCssPath, FileMode.OpenOrCreate))
                using (var sw = new StreamWriter(fsCLD, Encoding.UTF8))
                {
                    sw.WriteLine("@charset \"utf-8\";");
                }
            }
        }

        /// <summary>
        /// 载入 Html Help Workshop 的安装位置信息。
        /// </summary>
        private void LoadHHWInstalledPath()
        {
            if (File.Exists(this.HHWInstalledPath) == false)
            {
                var result = LMessageBox.Show("请指定 Microsoft Html Help Workshop 所在的磁盘路径：\r\n" +
                    "　　通常它的文件名应是 Microsoft HTML Help Workshop.exe 。\r\n\r\n　　要现在指定吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Information);

                if (result != MessageBoxResult.Yes) return;
                SetHtmlHelpWorkshopFullName();
            }
        }

        /// <summary>
        /// 指定安装的 Html Help Workshop 的路径。
        /// 程序会自动通过注册表寻找安装的 Html Help Workshop。
        /// 但有时我们下载的不是微软公司的官方版本，很可能是网友修改过的汉化版——此时注册表中未必有这个安装位置信息。
        /// </summary>
        private void SetHtmlHelpWorkshopFullName()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            ofd.Title = Globals.AppName + "请指定 Microsoft Html Help Workshop 路径：";
            ofd.Filter = "可执行文件(*.exe)|*.exe";

            if (ofd.ShowDialog() == true)
            {
                App.ConfigManager.Set("HHWInstalledPath", ofd.FileName);
                this.hhwInstalledPath = ofd.FileName;

                var fileInfo = new FileInfo(this.hhwInstalledPath);
                var hhcInstalledPath = fileInfo.DirectoryName + "\\hhc.exe";
                if (File.Exists(hhcInstalledPath))
                {
                    Globals.hhcInstalledPath = hhcInstalledPath;
                    App.ConfigManager.Set("HHCInstalledPath", hhcInstalledPath);
                }
            }
        }

        /// <summary>
        /// 载入 Html Help Workshop 的编译器的安装位置信息。
        /// </summary>
        private void LoadHHCInstalledPath()
        {
            if (File.Exists(this.HHCInstalledPath) == false)
            {
                var result = LMessageBox.Show("请指定 Microsoft Html Help Workshop 的编译器文件所在的磁盘路径：\r\n" +
                    "　　通常它的文件名应是 hhc.exe 。\r\n\r\n　　要现在指定吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Information);

                if (result != MessageBoxResult.Yes) return;
                SetHtmlHelpWorkshopCompilerFullName();
            }
        }

        /// <summary>
        /// 指定安装的 Html Help Workshop 的路径。
        /// 程序会自动通过注册表寻找安装的 Html Help Workshop。
        /// 但有时我们下载的不是微软公司的官方版本，很可能是网友修改过的汉化版——此时注册表中未必有这个安装位置信息。
        /// </summary>
        private void SetHtmlHelpWorkshopCompilerFullName()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            ofd.Title = Globals.AppName + "请指定 Microsoft Html Help Workshop 编译器（hhc.exe）的路径：";
            ofd.Filter = "可执行文件(*.exe)|*.exe";

            if (ofd.ShowDialog() == true)
            {
                App.ConfigManager.Set("HHCInstalledPath", ofd.FileName);
                this.hhcInstalledPath = ofd.FileName;

                if (File.Exists(Globals.hhwInstalledPath) == false)
                {
                    var fileInfo = new FileInfo(this.hhwInstalledPath);
                    var hhwInstalledPath = fileInfo.DirectoryName + "\\Microsoft Html Help Workshop.exe";
                    if (File.Exists(hhwInstalledPath))
                    {
                        Globals.hhwInstalledPath = hhwInstalledPath;
                        App.ConfigManager.Set("HHWInstalledPath", hhwInstalledPath);
                    }
                }
            }
        }

        /// <summary>
        /// 与InputBox中的同名方法不同：这里要考虑路径分割符和盘符。
        /// </summary>
        /// <param name="text">要检查的源文本。</param>
        /// <param name="highLevel">是否只允许字母、数字、下划线字符。</param>
        /// <returns>为真表示可以用作文件名。</returns>
        public static bool ValidateFilePath(string text, bool highLevel)
        {
            if (string.IsNullOrEmpty(text))
                return false;

            if (highLevel)
            {
                return Regex.Match(text, @"([a-zA-Z]:\\){0,1}([a-zA-Z0-9_\.]\\{1,})").Success;
            }

            if (text.Contains("/") ||
               //text.Contains("\\") ||     //路径中必须带分隔符，故这行不能加。
               //text.Contains(":") ||      //完整的路径中带盘符，故冒号不能加。
               text.Contains("*") ||
               text.Contains("\"") ||
               text.Contains("<") ||
               text.Contains(">") ||
               text.Contains("|") ||
               text.Contains("[") || //方括号会导致图像链接文本切分时识别出错。
               text.Contains("]"))
            {
                return false;
            }

            //带冒号的路径要考虑盘符的问题。
            if (text.Contains(":") || text.Contains("\\"))
            {
                if (text.Length <= 3)
                    return false;

                var subText1 = text.Substring(0, 3);
                if (subText1.EndsWith(":\\") == false)
                    return false;
                var firstChar = subText1[0];
                if (((firstChar >= 'a' && firstChar <= 'z') || (firstChar >= 'A' && firstChar <= 'Z')) == false)
                    return false;
                //以上可以确定不是盘符

                var subText2 = text.Substring(3);
                if (subText2.Contains(":"))
                    return false;//除盘符外，其它位置不应存在冒号

                if (subText2.StartsWith("\\") || subText2.EndsWith("\\"))
                    return false;
                //除开盘符后，路径的剩余部分不应以反斜杠开头；
                //即使不是以反斜杠开头，也仍然可能是个目录（目录以反斜杠结尾）
            }


            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                if (i == 2 && c == ':') continue;

                //全角标点均可用，半角标点不允许前列特殊字符。另，
                //半角波形符用于资源文件夹，不可用。
                //下划线用于目录元文件，不可用。
                //反引号符备用，不可用。

                //与InputBox不同，这里要允许冒号（盘符要用冒号）。
                if (@"""#%&()+,;<=>?@|".IndexOf(c) >= 0) return false;

                //与InputBox下的同名方法不同：这里要允许存在路径分隔符和冒号（盘符要用）
                if ("/\\!@$^-_{}',.～·＠＃￥％…＆＊（）【】｛｝｜；‘’“”：，。《〈〉》？".IndexOf(c) >= 0) continue;

                //全角字符就算是特殊符号也是可以的
                //全角小写字母
                if (c == '、') continue;
                if (c >= 'ａ' && c <= 'ｚ') continue;
                //全角大写字母
                if (c >= 'Ａ' && c <= 'Ｚ') continue;
                //！＂＃＄％＆＇（）＊＋，－．／
                if (c >= '！' && c <= '／') continue;
                //全角数字
                if (c >= '０' && c <= '９') continue;
                //：；＜＝＞？＠
                if (c >= '：' && c <= '＠') continue;
                //［＼］＾＿｀
                if (c >= '［' && c <= '｀') continue;
                //｛｜｝～
                if (c >= '｛' && c <= '～') continue;
                //￠￡￢￣￤￥
                if (c >= '￠' && c <= '￥') continue;
                //全角字符就算是特殊符号也是可以的

                //全角问号？就是>0x9fbb的
                if (c > 0x9fbb)
                {
                    if (c == '§') continue;
                    if (c == '☆') continue;
                    if (c == '★') continue;
                    if (c == '○') continue;
                    if (c == '●') continue;
                    if (c == '◎') continue;
                    if (c == '◇') continue;
                    if (c == '◆') continue;
                    if (c == '□') continue;
                    if (c == '■') continue;
                    if (c == '△') continue;
                    if (c == '▲') continue;
                    if (c == '※') continue;
                    if (c == '〓') continue;
                    if (c == '＃') continue;
                    if (c == '＆') continue;
                    if (c == '＠') continue;
                    if (c == '＿') continue;
                    if (c == '￣') continue;

                    return false;
                }

                if (c < 0x4e00)
                {
                    if (c == '\\') continue;
                    if (c == ':') continue;
                    if (c >= '0' && c <= '9') continue;
                    if (c == '_') continue;
                    if (c == '+') continue;
                    if (c == '-') continue;
                    if (c == '.') continue;//后缀名需要
                    if (c == '=') continue;
                    if (c == ' ') continue;
                    if (c == '(') continue;
                    if (c == ')') continue;
                    if (c == '）') continue;
                    if (c == '（') continue;
                    if (c >= '⑴' && c <= '⒇') continue;
                    if (c >= '①' && c <= '⑩') continue;
                    if (c >= '⒈' && c <= '⒛') continue;
                    if (c >= 'A' && c <= 'Z') continue;
                    if (c >= 'a' && c <= 'z') continue;

                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 生成CHM工程所需要的目录文件。
        /// </summary>
        /// <param name="wtvi">Markdown 文件条目或者（非资源的）目录条目。</param>
        /// <param name="prefixTab">第一层应传入空字符串，不需要\t。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀文本。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回带特殊字符的或无效文件的物理路径列表。</returns>
        private List<string> CreateCHMContentFile(WorkspaceTreeViewItem wtvi, string prefixTab, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (wtvi == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            var invalidatedFilePaths = new List<string>();

            var itemType = wtvi.ItemType;

            switch (itemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = wtvi.Title;

                        var chmImageIndexText = wtvi.ChmImageIndex.ToString();

                        var path = wtvi.FullPath.Substring(Globals.PathOfWorkspace.Length);
                        var filePath = Globals.PathOfWorkspace + path;

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的文档。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的文件。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break; ;
                            }
                        }
                        #endregion

                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"{prefixTab}<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"{prefixTab}\t<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"{prefixTab}\t<param name=\"Local\" value=\"{path}\">\r\n");
                        if (chmImageIndexText != "00")
                        {
                            sBuilder.Append($"{prefixTab}\t<param name=\"ImageNumber\" value=\"{chmImageIndexText}\">\r\n");
                        }
                        sBuilder.Append($"</OBJECT>\r\n");

                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {

                        var title = wtvi.Title;
                        var filePath = wtvi.FullPath;
                        filePath = GetMetaFilePathOfDirectory(filePath);

                        //要判断元文件是否被加密、被标记为“废弃”
                        //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的文档。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的文件。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break; ;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"{prefixTab}<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"{prefixTab}<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"{prefixTab}<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append($"</OBJECT>\r\n");

                        if (wtvi.Items.Count > 0)
                        {
                            prefixTab += "\t";
                            sBuilder.Append($"{prefixTab}<UL>\r\n");

                            foreach (WorkspaceTreeViewItem childItem in wtvi.Items)
                            {
                                var subInvalidatedFilePaths = CreateCHMContentFile(childItem, prefixTab, sBuilder, ref encryptedPaths, ref abortedPaths);
                                invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                            }

                            sBuilder.Append($"{prefixTab}</UL>\r\n");
                        }

                        break;
                    }
            }
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的目录文件。
        /// </summary>
        /// <param name="entryNode">Markdown 文件条目或者（非资源的）目录条目。</param>
        /// <param name="prefixTab">第一层应传入空字符串，不需要\t。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀文本。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回带特殊字符的或无效文件的物理路径列表。</returns>
        private List<string> CreateCHMContentFile(XmlNode entryNode, string prefixTab, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (entryNode == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            var invalidatedFilePaths = new List<string>();

            var itemTypeAttr = entryNode.GetAttribute("ItemType");
            WorkspaceTreeViewItem.Type itemType;
            if (Enum.TryParse<WorkspaceTreeViewItem.Type>(itemTypeAttr.Value, out itemType) == false)
            {
                itemType = WorkspaceTreeViewItem.Type.Folder;
            }

            switch (itemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrChmImageIndexAttr = entryNode.GetAttribute("ChmImageIndex");

                        var attrPath = entryNode.GetAttribute("Path");
                        if (attrPath != null)
                        {
                            var path = attrPath.Value;

                            var filePath = Globals.PathOfWorkspace + attrPath.Value;

                            #region 判断是否需要跳过此文件
                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                                if (isEncrypted)
                                {
                                    encryptedPaths.Add(filePath);
                                    break;//忽略被加密的文档。
                                }
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                if (CustomMarkdownSupport.IsFileAborted(filePath))
                                {
                                    abortedPaths.Add(filePath);
                                    break; ;
                                }
                            }
                            #endregion

                            if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                            {
                                path = path.Substring(Globals.PathOfWorkspace.Length);
                            }

                            if (path.EndsWith(".md"))
                            {
                                path = path.Substring(0, path.Length - 3) + ".html";
                            }

                            if (File.Exists(Globals.PathOfWorkspace + path) == false)
                            {
                                invalidatedFilePaths.Add(path);
                            }

                            path = CustomMarkdownSupport.UrlEncode(path);

                            sBuilder.Append($"{prefixTab}<LI> <OBJECT type=\"text/sitemap\">\r\n");
                            sBuilder.Append($"{prefixTab}\t<param name=\"Name\" value=\"{title}\">\r\n");
                            sBuilder.Append($"{prefixTab}\t<param name=\"Local\" value=\"{path}\">\r\n");
                            if (attrChmImageIndexAttr != null && attrChmImageIndexAttr.Value != "00")
                            {
                                if (int.TryParse(attrChmImageIndexAttr.Value, out int acii))
                                {
                                    sBuilder.Append($"{prefixTab}\t<param name=\"ImageNumber\" value=\"{acii}\">\r\n");
                                }
                            }
                            sBuilder.Append($"</OBJECT>\r\n");

                        }
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {

                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        var filePath = attrPath.Value;
                        if (filePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()) == false)
                        {
                            filePath = Globals.PathOfWorkspace + filePath;
                        }

                        filePath = GetMetaFilePathOfDirectory(filePath);

                        //要判断元文件是否被加密、被标记为“废弃”
                        //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的文档。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的文件。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break; ;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"{prefixTab}<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"{prefixTab}<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"{prefixTab}<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append($"</OBJECT>\r\n");

                        if (entryNode.ChildNodes.Count > 0)
                        {
                            prefixTab += "\t";
                            sBuilder.Append($"{prefixTab}<UL>\r\n");

                            foreach (XmlNode childNode in entryNode.ChildNodes)
                            {
                                var subInvalidatedFilePaths = CreateCHMContentFile(childNode, prefixTab, sBuilder, ref encryptedPaths, ref abortedPaths);
                                invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                            }

                            sBuilder.Append($"{prefixTab}</UL>\r\n");
                        }

                        break;
                    }
            }
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的目录文件。
        /// </summary>
        /// <param name="directoryInfo"></param>
        /// <param name="prefixTab">第一层应传入空字符串，不需要\t。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀文本。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回带特殊字符的物理路径列表。</returns>
        private List<string> CreateCHMContentFile(DirectoryInfo directoryInfo, string prefixTab, string prefixPath, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (directoryInfo == null || directoryInfo.Exists == false || prefixPath == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            if (prefixPath != "" && prefixPath.EndsWith("/") == false) prefixPath += "/";

            sBuilder.Append($"{prefixTab}<UL>\r\n");

            List<string> invalidatedFilePaths = new List<string>();

            List<ChmContentEntry> chmContentEntries = new List<ChmContentEntry>();

            var files = directoryInfo.GetFiles();
            if (files.Length > 0)
            {
                foreach (var fileInfo in files)
                {
                    if (fileInfo.FullName.ToLower().EndsWith(".html") == false) continue;
                    if (fileInfo.FullName.ToLower().EndsWith("~.html")) continue;

                    if (fileInfo.Name.ToLower() == ("_" + directoryInfo + ".html").ToLower()) continue;//这个文件是指向CHM目录节点的文件。不在这里添加目录引用。

                    if (ValidateFilePath(fileInfo.FullName, true) == false)
                    {
                        invalidatedFilePaths.Add(fileInfo.FullName);
                    }

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(fileInfo.FullName, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(fileInfo.FullName);
                            continue;//忽略被加密的文档。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的文件。
                        if (CustomMarkdownSupport.IsFileAborted(fileInfo.FullName))
                        {
                            abortedPaths.Add(fileInfo.FullName);
                            continue;
                        }
                    }
                    #endregion

                    string contentEntryName = GetTitleOfMdFile(fileInfo.FullName);

                    if (string.IsNullOrEmpty(contentEntryName))
                    {
                        var shortName = fileInfo.Name;
                        if (shortName.StartsWith("_") && shortName.Length > 1)
                        {
                            shortName = shortName.Substring(1);
                        }

                        if (shortName.ToLower() == "index.html" || shortName.ToLower() == "_index.html")
                        {
                            shortName = "目录";
                        }
                        else
                        {
                            if (shortName.ToLower().EndsWith(".html") && shortName.Length > 5)
                            {
                                shortName = shortName.Substring(0, shortName.Length - 5);
                            }
                        }

                        contentEntryName = shortName;
                    }

                    chmContentEntries.Add(new ChmContentEntry()
                    {
                        FileSystemInfo = fileInfo,
                        ShortName = fileInfo.Name,
                        Title = contentEntryName,
                        IsFile = true,
                    });
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            if (subDirectories.Length > 0)
            {
                //prefixTab += "\t";
                foreach (var subDirectory in subDirectories)
                {
                    if (subDirectory.FullName.EndsWith("~")) continue;

                    var subFiles = subDirectory.GetFiles();
                    var htmlCount = 0;
                    string directoryMetaFile = null;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html"))
                        {
                            htmlCount++;
                            if (subfile.Name.ToLower() == ("_" + subDirectory.Name + ".html").ToLower())
                            {
                                directoryMetaFile = subfile.Name;
                            }
                        }
                    }

                    //要判断元文件是否被加密、被标记为“废弃”
                    //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！
                    var subDirectoryMetaFilePath = subDirectory.FullName + "\\" + directoryMetaFile;

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(subDirectoryMetaFilePath, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(subDirectoryMetaFilePath);
                            continue;//忽略被加密的文档。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的文件。
                        if (CustomMarkdownSupport.IsFileAborted(subDirectoryMetaFilePath))
                        {
                            abortedPaths.Add(subDirectoryMetaFilePath);
                            continue;
                        }
                    }
                    #endregion

                    var shortTitle = GetTitleOfMdFile(subDirectoryMetaFilePath);
                    if (string.IsNullOrEmpty(shortTitle))
                    {
                        shortTitle = subDirectory.Name;
                    }

                    chmContentEntries.Add(new ChmContentEntry()
                    {
                        IsDirectory = true,
                        FileSystemInfo = subDirectory,
                        ShortName = subDirectory.Name,
                        Title = shortTitle,
                        DirectoryMetaFile = directoryMetaFile,
                    });

                }
            }

            List<ChmContentEntry> tempList2 = new List<ChmContentEntry>();
            List<ChmContentEntry> tempList3 = new List<ChmContentEntry>();

            foreach (var c in chmContentEntries)
            {
                if (c.ShortName.StartsWith("_"))
                {
                    tempList2.Add(c);
                }
                else
                {
                    tempList3.Add(c);
                }
            }

            tempList2.Sort(new ChmContentEntryCompare());
            tempList3.Sort(new ChmContentEntryCompare());

            foreach (var ce in tempList2)
            {
                if (ce.IsFile)
                {
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{CustomMarkdownSupport.UrlEncode(prefixPath)}{ce.UrlName}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");
                }
                else if (ce.IsDirectory)
                {
                    prefixTab += "\t";
                    //先加目录，即使是空目录也应该添加一条目录条目
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");

                    //目录节点在CHM中指向的文件应是该目录下名为“_目录短名.html”的文件。
                    if (string.IsNullOrWhiteSpace(ce.DirectoryMetaFile) == false)
                    {
                        sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\\{ce.DirectoryMetaFile}\">\r\n");
                    }

                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");

                    var subPrefixPath = prefixPath + ce.UrlName;

                    //因为是递归的，所以不能直接用下面这行：
                    //hasInvalidatedFilePath = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                    var subInvalidatedFilePaths = CreateCHMContentFile(ce.FileSystemInfo as DirectoryInfo, prefixTab, subPrefixPath, sBuilder, ref encryptedPaths, ref abortedPaths);
                    if (subInvalidatedFilePaths.Count > 0)
                    {
                        invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                    }
                    prefixTab = prefixTab.Substring(0, prefixTab.Length - 1);
                }
            }

            foreach (var ce in tempList3)
            {
                if (ce.IsFile)
                {
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");
                }
                else if (ce.IsDirectory)
                {
                    prefixTab += "\t";
                    //先加目录，即使是空目录也应该添加一条目录条目
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{FormatDocumentTitle(ce.Title)}\">\r\n");

                    //目录节点在CHM中指向的文件应是该目录下名为“_目录短名.html”的文件。
                    if (string.IsNullOrWhiteSpace(ce.DirectoryMetaFile) == false)
                    {
                        sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\\{ce.DirectoryMetaFile}\">\r\n");
                    }

                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");

                    var subPrefixPath = prefixPath + ce.UrlName;

                    //因为是递归的，所以不能直接用下面这行：
                    //invalidatedFilePaths = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                    var subInvalidatedFilePaths = CreateCHMContentFile(ce.FileSystemInfo as DirectoryInfo, prefixTab, subPrefixPath, sBuilder, ref encryptedPaths, ref abortedPaths);
                    if (invalidatedFilePaths.Count > 0)
                    {
                        invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                    }
                    prefixTab = prefixTab.Substring(0, prefixTab.Length - 1);
                }
            }

            sBuilder.Append($"{prefixTab}</UL>\r\n");
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的索引文件。与目录文件不同，索引文件不分层级。
        /// </summary>
        /// <param name="prefixPath">表示目录层级关系的前缀。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回含有特殊字符的文件路径的列表。</returns>
        private List<string> CreateCHMIndexFile(WorkspaceTreeViewItem wtvi, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (wtvi == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            var invalidatedFilePaths = new List<string>();

            var itemType = wtvi.ItemType;

            switch (itemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = wtvi.Title;

                        var filePath = wtvi.FullPath;

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的文档。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的文件。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"\t<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append("\t\t</OBJECT>\r\n");

                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        var title = wtvi.Title;
                        var filePath = wtvi.FullPath;
                        filePath = GetMetaFilePathOfDirectory(filePath);

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的元文档及整个子目录。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的元文档及整个子目录。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"\t<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append($"\t\t</OBJECT>\r\n");

                        if (wtvi.Items.Count > 0)
                        {
                            foreach (WorkspaceTreeViewItem childItem in wtvi.Items)
                            {
                                var subInvalidatedFilePaths = CreateCHMIndexFile(childItem, sBuilder, ref encryptedPaths, ref abortedPaths);
                                invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                            }
                        }

                        break;
                    }
            }
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的索引文件。与目录文件不同，索引文件不分层级。
        /// </summary>
        /// <param name="directoryNode">基准目录，通常应传入工作区目录。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回含有特殊字符的文件路径的列表。</returns>
        private List<string> CreateCHMIndexFile(XmlNode entryNode, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (entryNode == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            var invalidatedFilePaths = new List<string>();

            var itemTypeAttr = entryNode.GetAttribute("ItemType");
            WorkspaceTreeViewItem.Type itemType;
            if (Enum.TryParse<WorkspaceTreeViewItem.Type>(itemTypeAttr.Value, out itemType) == false)
            {
                itemType = WorkspaceTreeViewItem.Type.Folder;
            }

            switch (itemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        if (attrPath != null)
                        {
                            var path = attrPath.Value;
                            var filePath = Globals.PathOfWorkspace + attrPath.Value;

                            #region 判断是否需要跳过此文件
                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                //编译工作区时忽略已加密文件
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                                if (isEncrypted)
                                {
                                    encryptedPaths.Add(filePath);
                                    break;//忽略被加密的文档。
                                }
                            }

                            if (Globals.MainWindow.IgnoreAbortedFiles)
                            {
                                //编译工作区时忽略被标记为“废弃”的文件。
                                if (CustomMarkdownSupport.IsFileAborted(filePath))
                                {
                                    abortedPaths.Add(filePath);
                                    break;
                                }
                            }
                            #endregion

                            if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                            {
                                path = path.Substring(Globals.PathOfWorkspace.Length);
                            }

                            if (path.EndsWith(".md"))
                            {
                                path = path.Substring(0, path.Length - 3) + ".html";
                            }

                            if (File.Exists(Globals.PathOfWorkspace + path) == false)
                            {
                                invalidatedFilePaths.Add(path);
                            }

                            path = CustomMarkdownSupport.UrlEncode(path);

                            sBuilder.Append($"\t<LI> <OBJECT type=\"text/sitemap\">\r\n");
                            sBuilder.Append($"\t\t<param name=\"Name\" value=\"{title}\">\r\n");
                            sBuilder.Append($"\t\t<param name=\"Local\" value=\"{path}\">\r\n");
                            sBuilder.Append("\t\t</OBJECT>\r\n");

                        }
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        var title = "";
                        var attrTitle = entryNode.GetAttribute("Title");
                        if (attrTitle != null && string.IsNullOrWhiteSpace(attrTitle.Value) == false)
                        {
                            title = attrTitle.Value;
                        }

                        var attrPath = entryNode.GetAttribute("Path");
                        var filePath = attrPath.Value;
                        if (filePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()) == false)
                        {
                            filePath = Globals.PathOfWorkspace + filePath;
                        }

                        filePath = GetMetaFilePathOfDirectory(filePath);

                        #region 判断是否需要跳过此文件
                        if (Globals.MainWindow.IgnoreEncryptedFiles)
                        {
                            //编译工作区时忽略已加密文件
                            string password, passwordTip;
                            var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(filePath, out password, out passwordTip);
                            if (isEncrypted)
                            {
                                encryptedPaths.Add(filePath);
                                break;//忽略被加密的元文档及整个子目录。
                            }
                        }

                        if (Globals.MainWindow.IgnoreAbortedFiles)
                        {
                            //编译工作区时忽略被标记为“废弃”的元文档及整个子目录。
                            if (CustomMarkdownSupport.IsFileAborted(filePath))
                            {
                                abortedPaths.Add(filePath);
                                break;
                            }
                        }
                        #endregion

                        var path = filePath;
                        if (path.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
                        {
                            path = path.Substring(Globals.PathOfWorkspace.Length);
                        }

                        if (path.EndsWith(".md"))
                        {
                            path = path.Substring(0, path.Length - 3) + ".html";
                        }

                        if (File.Exists(Globals.PathOfWorkspace + path) == false)
                        {
                            invalidatedFilePaths.Add(path);
                        }

                        path = CustomMarkdownSupport.UrlEncode(path);

                        sBuilder.Append($"\t<LI> <OBJECT type=\"text/sitemap\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Name\" value=\"{title}\">\r\n");
                        sBuilder.Append($"\t\t<param name=\"Local\" value=\"{path}\">\r\n");
                        sBuilder.Append($"\t\t</OBJECT>\r\n");

                        if (entryNode.ChildNodes.Count > 0)
                        {
                            foreach (XmlNode childNode in entryNode.ChildNodes)
                            {
                                var subInvalidatedFilePaths = CreateCHMIndexFile(childNode, sBuilder, ref encryptedPaths, ref abortedPaths);
                                invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                            }
                        }

                        break;
                    }
            }
            return invalidatedFilePaths;
        }

        /// <summary>
        /// 生成CHM工程所需要的索引文件。与目录文件不同，索引文件不分层级。
        /// </summary>
        /// <param name="directoryNode">基准目录，通常应传入工作区目录。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回含有特殊字符的文件路径的列表。</returns>
        private List<string> CreateCHMIndexFile(DirectoryInfo directoryInfo, string prefixPath, StringBuilder sBuilder, ref List<string> encryptedPaths, ref List<string> abortedPaths)
        {
            if (directoryInfo == null || directoryInfo.Exists == false || prefixPath == null || sBuilder == null)
                return new List<string>();

            if (prefixPath != "" && prefixPath.EndsWith("/") == false) prefixPath += "/";

            List<string> invalidatedFilePaths = new List<string>();

            var files = directoryInfo.GetFiles();
            if (files.Length > 0)
            {
                foreach (var fileInfo in files)
                {
                    if (fileInfo.FullName.ToLower().EndsWith(".html") == false) continue;
                    if (fileInfo.FullName.ToLower().EndsWith("~.html")) continue;

                    if (ValidateFilePath(fileInfo.FullName, true) == false)
                    {
                        invalidatedFilePaths.Add(fileInfo.FullName);
                    }

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(fileInfo.FullName, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(fileInfo.FullName);
                            break;//忽略被加密的元文档及整个子目录。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的元文档及整个子目录。
                        if (CustomMarkdownSupport.IsFileAborted(fileInfo.FullName))
                        {
                            abortedPaths.Add(fileInfo.FullName);
                            break;
                        }
                    }
                    #endregion

                    string contentEntryName = GetTitleOfMdFile(fileInfo.FullName);

                    if (string.IsNullOrEmpty(contentEntryName))
                    {
                        var shortName = fileInfo.Name;
                        if (shortName.StartsWith("_") && shortName.Length > 1)
                        {
                            shortName = shortName.Substring(1);
                        }

                        if (shortName.ToLower() == "index.html" || shortName.ToLower() == "_index.html")
                        {
                            shortName = "目录";
                        }
                        else
                        {
                            if (shortName.ToLower().EndsWith(".html") && shortName.Length > 5)
                            {
                                shortName = shortName.Substring(0, shortName.Length - 5);
                            }
                        }

                        contentEntryName = shortName;
                    }

                    sBuilder.Append("\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"\t\t<param name =\"Name\" value=\"{contentEntryName}\">\r\n");
                    sBuilder.Append($"\t\t<param name =\"Local\" value=\"{prefixPath}{CustomMarkdownSupport.UrlEncode(fileInfo.Name)}\">\r\n");
                    sBuilder.Append("\t\t</OBJECT>\r\n");
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            if (subDirectories.Length > 0)
            {
                foreach (var subDirectory in subDirectories)
                {
                    var subFiles = subDirectory.GetFiles();
                    string directoryMetaFile = null;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html"))
                        {
                            if (subfile.Name.ToLower() == ("_" + subDirectory.Name + ".html").ToLower())
                            {
                                directoryMetaFile = subfile.Name;
                            }
                        }
                    }

                    //要判断元文件是否被加密、被标记为“废弃”
                    //如果目录元文件被标记为“废弃”，则整个目录被跳过！！！
                    var subDirectoryMetaFilePath = subDirectory.FullName + "\\" + directoryMetaFile;

                    #region 判断是否需要跳过此文件
                    if (Globals.MainWindow.IgnoreEncryptedFiles)
                    {
                        //编译工作区时忽略已加密文件
                        string password, passwordTip;
                        var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(subDirectoryMetaFilePath, out password, out passwordTip);
                        if (isEncrypted)
                        {
                            encryptedPaths.Add(subDirectoryMetaFilePath);
                            continue;//忽略被加密的文档。
                        }
                    }

                    if (Globals.MainWindow.IgnoreAbortedFiles)
                    {
                        //编译工作区时忽略被标记为“废弃”的文件。
                        if (CustomMarkdownSupport.IsFileAborted(subDirectoryMetaFilePath))
                        {
                            abortedPaths.Add(subDirectoryMetaFilePath);
                            continue;
                        }
                    }
                    #endregion

                    if (subDirectory.FullName.EndsWith("~")) continue;

                    var htmlCount = 0;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html")) htmlCount++;
                    }

                    if (htmlCount > 0)
                    {
                        //索引文件不加目录
                        //sb.Append($"\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                        //sb.Append($"\t\t<param name =\"Name\" value=\"{subDirectiry.Name}\">\r\n");
                        //sb.Append($"\t\t</OBJECT>\r\n");

                        var subPrefixPath = CustomMarkdownSupport.UrlEncode(subDirectory.FullName);

                        //因为是递归的，所以不能直接用下面这行：
                        //hasInvalidatedFilePath = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);
                        var subInvalidatedFilePaths = CreateCHMIndexFile(subDirectory, subPrefixPath, sBuilder, ref encryptedPaths, ref abortedPaths);
                        if (subInvalidatedFilePaths.Count > 0)
                        {
                            invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                        }
                    }
                }
            }

            return invalidatedFilePaths;
        }

        /// <summary>
        /// 根据指定的 Markdown 文件读取其中的标题文本。
        /// 标题文本通常是第一个以“%”开头的行，如找不到，则寻找被“标题＞＞”和“＜＜标题”包围的文本作为标题。
        /// </summary>
        public static string GetTitleOfMdFile(string mdFileFullName)
        {
            if (mdFileFullName != null && mdFileFullName.ToLower().EndsWith(".html"))
            {
                mdFileFullName = mdFileFullName.Substring(0, mdFileFullName.Length - 5) + ".md";
            }

            if (File.Exists(mdFileFullName) == false) return "";


            IEnumerable<string> lines;

            bool isEncrypted = false;
            string password, passwordTip;
            isEncrypted = CustomMarkdownSupport.IsFileEncrypted(mdFileFullName, out password, out passwordTip);
            if (isEncrypted)
            {
                var allText = File.ReadAllText(mdFileFullName);
                var contentText = SetPasswordPanel.TextDecrypt(allText.Substring(allText.IndexOf("\r\n") + 2).ToCharArray(), "DyBj#PpBb");
                lines = contentText.Split(new string[] { "\r\n" }, StringSplitOptions.None);
            }
            else
            {
                lines = File.ReadLines(mdFileFullName);
            }

            int lineIndex = 0;
            foreach (string s in lines)
            {
                if (lineIndex > 9) break;//禁止标题出现在前10行之外——如果读取整个文件来寻找个标题也太恐怖了。

                if (s.StartsWith("%"))
                {
                    //不需要考虑“title>”这样的特殊写法，因为编译前都会自动格式化成半角百分号
                    var ts = s.Substring(1).Trim();

                    if (ts.Length >= 64)
                    {
                        return ts.Substring(0, 63);
                    }
                    else return ts;
                }

                var title = CustomMarkdownSupport.GetDocumentTitle(s);
                if (string.IsNullOrWhiteSpace(title) == false) return title;

                int startIndex = s.IndexOf("标题＞＞");
                int endIndex = s.IndexOf("＜＜标题");
                if (startIndex >= 0 && endIndex >= 0)
                {
                    return s.Substring(startIndex + 4, endIndex - startIndex - 4);
                }
                lineIndex++;
            }

            return "";
        }

        private bool SaveModifiedDocuments()
        {
            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti != null && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count <= 0) return true;

            MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                Globals.AppName, MessageBoxButton.OKCancel, MessageBoxImage.Warning);
            if (r == MessageBoxResult.OK)
            {
                SaveAllDocumentsAndOptions();
                return true;
            }
            else return false;
        }

        /// <summary>
        /// 编译工作区及其下级目录下的所有 Markdown 文档。
        /// </summary>
        private void miCompileAllMdFilesOfWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (SaveModifiedDocuments() == false) return;

            if (miUtf8.IsChecked)
            {
                var result = LMessageBox.Show("当前选择的字符编码是 UTF-8，这种编码的 Html 不适合编译成 CHM 文档。要自动改成 GB18030 字符编码吗？",
                                    Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);

                if (result == MessageBoxResult.Cancel) return;
                if (result == MessageBoxResult.Yes)
                {
                    miGb18030.IsChecked = true;
                    miGb18030_Click(sender, e);
                }
            }

            if (ckxShowTitle.IsChecked != true)
            {
                var result = LMessageBox.Show("当前工作区管理器中显示的是文件名而不是文件标题，这可能导致编译出来的索引文件中的链接不是预期效果。\r\n　　要自动更改为显示标题吗？",
                                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    ckxShowTitle_Checked(sender, e);
                    SwitchShowTitleInWorkspaceManager();
                }
            }

            CompileWholeWorkspace();

            var charsetText = "GB18030";
            if (miUtf8.IsChecked)
            {
                charsetText = "UTF-8";
            }
            else if (miGb2312.IsChecked)
            {
                charsetText = "GB2312";
            }

            var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

            if (File.Exists(indexHtmlFilePath))
            {
                if (previewFrame.Source != null && previewFrame.Source.AbsolutePath == indexHtmlFilePath)
                {
                    previewFrame.Refresh();
                }
                else
                {
                    previewFrame.Source = new Uri(indexHtmlFilePath);
                }

                tcRightToolBar.SelectedItem = tiHtmlPreview;
                if (tcRightToolBar.ActualWidth < 100)
                {
                    cdMainEditArea.Width =
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                }
            }

            LMessageBox.Show($"已按照【{charsetText}】编码将工作区内所有 Markdown 文件编译为 Html 文件！",
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
        }

        /// <summary>
        /// 编译全工作区（含下级目录）下的所有 Markdown 文件。
        /// </summary>
        private void CompileWholeWorkspace()
        {
            List<FileSystemEntry> compiledFileEntries = new List<FileSystemEntry>();

            CompileAllMdFiles(ref compiledFileEntries);

            //生成一个html索引文档。
            //BuildIndexOfCompiledHtmlFiles(fileSystemEntries);   //旧版，根据目录
            BuildIndexOfCompiledHtmlFiles();                      //新版，根据工作区管理器条目布局
        }

        /// <summary>
        /// 打开创建好的属于当前工作区的 CHM 工程文件。
        /// </summary>
        private void miOpenCHMProject_Click(object sender, RoutedEventArgs e)
        {
            if (SaveModifiedDocuments() == false) return;

            LoadHHWInstalledPath();

            if (File.Exists(this.HHWInstalledPath) == false)
            {
                LMessageBox.Show("未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
                    "　　您可能需要到微软公司官方网站下载该工具并安装。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
            var chmProjectFilePath = prefixOfFiles + ".hhp";

            if (File.Exists(chmProjectFilePath))
            {
                //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
                //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

                //下面这个办法也不行，这貌似是 Linux 下的办法？
                //if (chmProjectFilePath.Contains(" "))
                //{
                //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
                //}

                //事实证明，给每个子目录或文件名（带空格）分别包围双引号的办法也行不通。
                //只能禁止工作区目录和用户创建的所有目录带空格了，还要提示用户选择的路径带空格。

                if (chmProjectFilePath.Contains(" "))
                {
                    LMessageBox.Show("您设置的工作区目录完整路径中含有空格，这会导致 Html Help Workshop 在编译时提示无法打开工程文件！\r\n　　请在弹出该消息框后手工选取工程文件位置再编译（办法是去除路径首尾自动添加的双引号）。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning, "", "001_常见问题与错误.html#can_not_find_chm_project");
                    System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                }
                else
                {
                    System.Diagnostics.Process.Start(this.HHWInstalledPath, chmProjectFilePath);
                }
            }
            else
            {
                LMessageBox.Show("尚未创建工程。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void miCompileCHMProjectWithoutCreateProject_Click(object sender, RoutedEventArgs e)
        {
            CompileChmProject(ChmCompileRange.WholeWorkSpace, true);
        }

        /// <summary>
        /// 直接编译创建好的属于当前工作区的 CHM 工程文件。
        /// </summary>
        private void miCompileCHMProject_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.ShowWarning("请在工作区管理器中选择要导出的分支！");
                return;
            }

            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.ShowWarning("请在工作区管理器中选择要导出的分支！（选中的分支节点只能是文件夹节点或文件节点。）");
                return;
            }

            switch (selItem.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                case WorkspaceTreeViewItem.Type.Folder:
                case WorkspaceTreeViewItem.Type.MetaFile:
                    break;
                default:
                    LMessageBox.ShowWarning("选中的分支节点只能是文件夹节点或文件节点！");
                    return;
            }

            var range = ChmCompileRange.WholeWorkSpace;
            var mi = sender as MenuItem;
            if (mi != null)
            {
                var tag = mi.Tag as string;
                if (Enum.TryParse<ChmCompileRange>(tag, out ChmCompileRange r))
                {
                    range = r;
                }
            }

            if (range == ChmCompileRange.SelectedBranch)
            {
                var answer = LMessageBox.Show("使用此功能需要清理工作区中已存在的 Html 文档，然后按 GB18030 编码重新编译当前分支涉及的 Markdown 文件。" +
                    "否则编译出的 CHM 文件中可能包括不属于当前分支的、多余的 Html 文档。\r\n\r\n　　要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (answer != MessageBoxResult.Yes)
                    return;

                // 清理工作区。
                DeleteHtmlFiles(Globals.PathOfWorkspace);

                CompileBranch(selItem);
            }

            chmCompileRange = range;
            CompileChmProject(range);
        }

        private void CompileBranch(WorkspaceTreeViewItem wtvi)
        {
            if (miGb18030.IsChecked != true)
            {
                miGb18030_Click(null, null);
            }

            if (wtvi == null) return;
            switch (wtvi.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        var title = wtvi.Title;
                        CustomMarkdownSupport.CompileOrGetHtmlFile(wtvi.FullPath, Globals.MainWindow.htmlHeadersCollapse,
                             CustomMarkdownSupport.PresentateHtmlSplitterType.WholeDocument, 1, ref title, false, false, false, 1.0, "", "", "", "", true);
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        var metaFilePath = GetMetaFilePathOfDirectory(wtvi.FullPath);
                        var title = wtvi.Title;
                        CustomMarkdownSupport.CompileOrGetHtmlFile(metaFilePath, Globals.MainWindow.htmlHeadersCollapse,
                             CustomMarkdownSupport.PresentateHtmlSplitterType.WholeDocument, 1, ref title, false, false, false, 1.0, "", "", "", "", true);
                        foreach (var item in wtvi.Items)
                        {
                            var subItem = item as WorkspaceTreeViewItem;
                            if (subItem == null) continue;
                            CompileBranch(subItem);
                        }
                        break;
                    }
            }
            return;
        }

        /// <summary>
        /// 编译 CHM 工程的范围。
        /// </summary>
        public enum ChmCompileRange
        {
            /// <summary>
            /// 当前工作区（默认）。
            /// </summary>
            WholeWorkSpace,
            /// <summary>
            /// 当前选定分区。
            /// </summary>
            SelectedBranch,
        }

        private void CompileChmProject(ChmCompileRange range, bool withoutCreate = false)
        {
            if (SaveModifiedDocuments() == false) return;

            //当硬盘空间很有限时，即使能够编译成功，生成的CHM文件的页面也会出错。
            //所有以必要提醒一下用户。

            //每次编译前都重新创建CHM工程文件
            //CreateChmProjectFiles() 方法会自动调用 WorkspaceManager.SaveWorkspaceTreeviewToXml();
            if (withoutCreate != true)
            {
                if (CreateChmProjectFiles(range) == false) return;
            }

            LoadHHCInstalledPath();

            if (File.Exists(this.HHCInstalledPath) == false)
            {
                LMessageBox.Show("未指定 Microsoft HTML Help Workshop 的编译器程序（hhc.exe）的磁盘路径，无法调用！\r\n" +
                    "　　您可能需要到微软公司官方网站下载该工具并安装。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            try
            {
                //修改功能，创建工程文件之前已询问过了。2017年7月20日
                //var result = LMessageBox.Show("编译 CHM 工程之前需要重新编译当前工作区中的 Markdown 文件吗？", Globals.AppName,
                //     MessageBoxButton.YesNo, MessageBoxImage.Question);
                //if (result == MessageBoxResult.Yes)
                //{
                //    miCompileAllMdFilesOfWorkspace_Click(sender, e);
                //}
                var suffix = "";
                if (range == ChmCompileRange.SelectedBranch)
                    suffix = "_";
                var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
                var chmProjectFilePath = prefixOfFiles + $"{suffix}.hhp";

                if (File.Exists(chmProjectFilePath))
                {
                    //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                    //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
                    //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

                    //下面这个办法也不行，这貌似是 Linux 下的办法？
                    //if (chmProjectFilePath.Contains(" "))
                    //{
                    //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
                    //}

                    //事实证明，给每个子目录或文件名（带空格）分别包围双引号的办法也行不通。
                    //只能禁止工作区目录和用户创建的所有目录带空格了，还要提示用户选择的路径带空格。

                    System.Diagnostics.Process process;

                    if (chmProjectFilePath.Contains(" "))
                    {
                        process = System.Diagnostics.Process.Start(this.HHCInstalledPath, $"\"{chmProjectFilePath}\"");
                    }
                    else
                    {
                        process = System.Diagnostics.Process.Start(this.HHCInstalledPath, chmProjectFilePath);
                    }

                    if (process != null)
                    {
                        process.EnableRaisingEvents = true;
                        process.Exited += CompileChmProjectProcess_Exited;
                    }
                }
                else
                {
                    LMessageBox.Show("尚未创建工程。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 指示当前编译的是分支还是整个工作区。
        /// </summary>
        private ChmCompileRange chmCompileRange = ChmCompileRange.WholeWorkSpace;

        private void CompileChmProjectProcess_Exited(object sender, EventArgs e)
        {
            var suffix = "";
            if (chmCompileRange == ChmCompileRange.SelectedBranch)
                suffix = "_";
            var path = Globals.PathOfWorkspace;
            DirectoryInfo di = new DirectoryInfo(path);
            path = di.FullName + di.Name + $"{suffix}.chm";

            if (File.Exists(path) == false)
            {
                //编译分支时，如果存在此消息框，会导致出错。
                //LMessageBox.Show("工作区目录下未找到编译好的 CHM 文件。请先尝试创建 CHM 工程并调用 Html Help Workshop 进行编译。",
                //    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (path.Contains(" "))
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{path}\"");
            }
            else
            {
                System.Diagnostics.Process.Start("explorer.exe", $"{path}");
            }
            chmCompileRange = ChmCompileRange.WholeWorkSpace;  // 恢复默认值。
        }

        /// <summary>
        /// 清理工作区及其下级目录中所有 Html 文件（无论是不是由 LME 生成的）。
        /// </summary>
        private void miClearCompiledHtmlFiles_Click(object sender, RoutedEventArgs e)
        {
            var result = LMessageBox.Show("此功能将删除工作区目录下所有Html文件，无论它是不是由本程序编译而成！\r\n\r\n　　真的要继续吗？",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);

            if (result != MessageBoxResult.Yes) return;

            DeleteHtmlFiles(Globals.PathOfWorkspace);
        }

        /// <summary>
        /// 清理指定目录及其下级目录中所有 Html 文件（无论是不是由 LME 生成的）。
        /// </summary>
        /// <param name="directoryPath">基准目录。从此目录开始递归清理。</param>
        private void DeleteHtmlFiles(string directoryPath)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directoryShortName = Path.GetDirectoryName(directoryPath);
            if (directoryShortName.EndsWith("~")) return;

            var directoryInfo = new DirectoryInfo(directoryPath);
            var files = directoryInfo.GetFiles();
            foreach (var file in files)
            {
                if (file.Extension.ToLower() != ".html") continue;
                if (Path.GetFileNameWithoutExtension(file.FullName).EndsWith("~")) continue;

                File.Delete(file.FullName);
            }

            var subDirectiries = directoryInfo.GetDirectories();
            foreach (var subDirectory in subDirectiries)
            {
                DeleteHtmlFiles(subDirectory.FullName);
            }
        }

        /// <summary>
        /// 切换“填空模式（FillBlank）”的开关。此属性用以决定是否将行内代码片段编译为带填空题的效果。
        /// 即是否令 Html 的 <code></code> 块初始前景色为透明——用户点击后再更改为其它色彩。
        /// </summary>
        private void miFillblankMode_Click(object sender, RoutedEventArgs e)
        {
            this.compileCodeToFillBlank =
            miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            App.WorkspaceConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "CompileCodeToFillBlank",
                HtmlOptionText = "<Code>..</Code> 块编译为填空",
            });
        }

        /// <summary>
        /// 删除历史工作区列表中选定条目。
        /// </summary>
        private void miDeleteHistoryWorkspaceEntry_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedIndex < 0)
            {
                LMessageBox.Show("请先选择要删除的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            lbxHistoryWorkspaces.Items.RemoveAt(lbxHistoryWorkspaces.SelectedIndex);

            StringBuilder sb = new StringBuilder();
            foreach (RecentDirectoryListBoxItem item in lbxHistoryWorkspaces.Items)
            {
                sb.Insert(0, "\r\n" + item.DirectoryPath);
            }

            File.WriteAllText(Globals.PathOfHistoryWorkspaceFileFullName, sb.ToString(), new UnicodeEncoding());
        }

        /// <summary>
        /// 删除历史导出区列表中选定的目录条目。
        /// </summary>
        private void miDeleteHistoryOutputEntry_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.SelectedIndex < 0)
            {
                LMessageBox.Show("请先选择要删除的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            lbxHistoryOutport.Items.RemoveAt(lbxHistoryOutport.SelectedIndex);

            StringBuilder sb = new StringBuilder();
            foreach (RecentDirectoryListBoxItem item in lbxHistoryOutport.Items)
            {
                sb.Insert(0, "\r\n" + item.DirectoryPath);
            }

            File.WriteAllText(Globals.PathOfHistoryOutputFileFullName, sb.ToString(), new UnicodeEncoding());
        }

        /// <summary>
        /// 清除“历史工作区（最近工作区列）”列表。
        /// </summary>
        private void miDeleteHistoryWorkspaceFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.Items.Count <= 0) return;

            var result = LMessageBox.Show("真的要清除“最近工作区列表”吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result != MessageBoxResult.Yes) return;

            try
            {
                if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    File.Delete(Globals.PathOfHistoryWorkspaceFileFullName);
                }

                lbxHistoryWorkspaces.Items.Clear();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 清除“历史导出区”列表。
        /// </summary>
        private void miDeleteHistoryOutputFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.Items.Count <= 0) return;

            var result = LMessageBox.Show("真的要清除“历史导出目录列表”吗？",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result != MessageBoxResult.Yes) return;

            try
            {
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    File.Delete(Globals.PathOfHistoryOutputFileFullName);
                }

                lbxHistoryOutport.Items.Clear();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName,
                    MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置插入一个“锚”。形如：[](@)。
        /// </summary>
        private void miInsertAnchor_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText.Replace("\r", "").Replace("\n", "").Replace(" ", "-").Replace("　", "-").Replace("\t", "-");
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"\r\n\r\n[](@{ChinesePinYin.ToChinesePinYinText(selText)} {selText})\r\n\r\n";
                var destSel = eti.EditorBase.SelectionStart + 8;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, selText.Length);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "\r\n\r\n[](@请输入锚ID)\r\n\r\n";
                var destSel = eti.EditorBase.SelectionStart + 8;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 6);
                }
            }
        }

        private void miInsertAnchorLinkMark_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText.Replace("\r", "").Replace("\n", "").Replace(" ", "-").Replace("　", "-").Replace("\t", "-");
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"<:{eti.EditorBase.SelectedText}:>";
                var destSel = eti.EditorBase.SelectionStart + 2;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, selText.Length);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "<::>";
                var destSel = eti.EditorBase.SelectionStart + 2;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 0);
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置的前一行插入一个“锚”。形如：[](@)。
        /// </summary>
        private void miInsertAnchorAtPreviewLine_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var line = eti.EditorBase.Document.GetLineByOffset(eti.EditorBase.SelectionStart);

            var selText = eti.EditorBase.SelectedText.Replace("\r", "").Replace("\n", "").Replace(" ", "-").Replace("　", "-").Replace("\t", "-");
            if (selText.Length > 0)
            {
                var anchorText = $"\r\n\r\n[](@{ChinesePinYin.ToChinesePinYinText(selText)} {selText})\r\n\r\n";
                eti.EditorBase.BeginChange();
                eti.EditorBase.Document.Replace(line.Offset, 0, anchorText);
                eti.EditorBase.EndChange();
            }
            else
            {
                var anchorText = "\r\n\r\n[](@请输入锚ID)\r\n\r\n";
                eti.EditorBase.BeginChange();
                eti.EditorBase.Document.Replace(line.Offset, 0, anchorText);
                eti.EditorBase.EndChange();
                var destSel = line.Offset + 8;
                if (destSel > 0)
                {
                    eti.EditorBase.Select(destSel, 6);
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置插入链接标记文本。形如：[]()。
        /// </summary>
        private void miInsertLinkMark_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText;
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"[{selText}]()";
                var destSel = eti.EditorBase.SelectionStart + 3 + selText.Length;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 0);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "[请输入链接名称]()";
                var destSel = eti.EditorBase.SelectionStart + 1;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 7);
                }
            }
        }

        /// <summary>
        /// 窗口最大化。
        /// </summary>
        private void miMaxSizeWindow_Click(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Maximized;
        }

        /// <summary>
        /// 切换“自动折行（TextAutoWrap）”的开关。开启时编辑区文本过长时会自动折行，否则总是在一行（显示横向滚动条）。
        /// </summary>
        private void miTextWrap_Click(object sender, RoutedEventArgs e)
        {
            this.TextAutoWrap =
            this.miTextAutoWrap.IsChecked = !this.miTextAutoWrap.IsChecked;

            RefreshTextAutoWrapToStatusBar();
        }

        /// <summary>
        /// 打开工作区管理器中选定条目所对应的 Markdown 文件、图像文件、目录。
        /// 图像文件、目录会调用 Windows 资源管理器打开；Markdown 文件则在右侧编辑区打开。
        /// </summary>
        private void miOpenFileOrFolder_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi.IsMarkdownFilePath)
            {
                OpenDocuments(new string[] { wtvi.FullPath });
            }
            else if (wtvi.IsDirectoryExists || wtvi.IsImageFileExist)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
            else
            {
                LMessageBox.Show("只有Markdown文档或目录或图像文件才可以打开！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 打开工作区管理器中选定的 Markdown 文档。
        /// 如果选中的条目是指向一个目录（但不是资源目录）且尚未创建该目录的元文件，则自动创建一个目录元文件。
        /// </summary>
        private void miOpenDocument_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果是MD文件，直接打开；
            //如果是Directory，打开它的默认MD文件；
            //如果是Image，调用Explorer使用系统默认图片应用程序打开。

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi.IsMarkdownFilePath)
            {
                OpenDocuments(new string[] { wtvi.FullPath });
            }
            else if (wtvi.IsDirectoryExists)
            {
                try
                {
                    DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
                    if (di.Name.EndsWith("~"))
                    {
                        LMessageBox.Show("以波型符结尾的是资源文件夹，不允许创建对应 Markdown 元文件。", Globals.AppName,
                          MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    var directoryMdFileFullName = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md";
                    CreateDirectoryMetaMdFile(di, directoryMdFileFullName);

                    OpenDocuments(new string[] { directoryMdFileFullName });
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                }
            }
            else if (wtvi.IsImageFileExist)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
        }

        /// <summary>
        /// 创建目录元文件。所谓目录元文件是为了 chm 提供的，创建 chm 目录时，会链接到目录而不是链接到目录下的文件条目。
        /// </summary>
        /// <param name="di">目录信息。</param>
        /// <param name="directoryMdFileFullName">目录对应元文件的路径。应该在该目录下并以“_目录名.md”为文件短名。</param>
        /// <param name="defaultTitle">默认的标题文本。</param>
        private static void CreateDirectoryMetaMdFile(DirectoryInfo di, string directoryMdFileFullName, string defaultTitle = null)
        {
            if (File.Exists(directoryMdFileFullName) == false)
            {
                using (StreamWriter sw = File.CreateText(directoryMdFileFullName))
                {
                    // DONE: 这里要和 WorkspaceTreeViewItem.cs 中的 OpenOrCreateDirectoryMetaFile() 方法保持内容上的一致
                    //File.WriteAllText(files[0], $"\r\n%{MainWindow.FormatDocumentTitle(di.Name)}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n{metaInlineEnviromentSpan}\r\n\r\n");
                    //sw.Write($"\r\n%{FormatDocumentTitle(string.IsNullOrWhiteSpace(defaultTitle) ? di.Name : defaultTitle)}\r\n\r\n；{DateTime.Now.ToString()}");
                    var isWorkspaceMetaFile = false;
                    var a = di.FullName.ToLower(); if (a.EndsWith("\\") == false) a += "\\";
                    var b = Globals.PathOfWorkspace.ToLower(); if (b.EndsWith("\\") == false) b += "\\";
                    if (a == b) isWorkspaceMetaFile = true;
                    var metaInlineEnviromentSpan = isWorkspaceMetaFile ? Properties.Resources.workspaceMetaFileInlineEnviromentTextSpan : Properties.Resources.metaFileInlineEnviromentTextSpan;
                    sw.Write($"\r\n%{FormatDocumentTitle(string.IsNullOrWhiteSpace(defaultTitle) ? di.Name : defaultTitle)}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n{metaInlineEnviromentSpan}\r\n\r\n");
                }
            }
        }

        private static string CreateMdFile(string fullPath, string defaultTitle = null)
        {
            try
            {
                var fi = new FileInfo(fullPath);
                //var newShortFileName = fi.Name;
                //newShortFileName = ChinesePinYin.ToChinesePinYinText(newShortFileName.Replace(" ", "-").Replace("　", "-").Replace("\t", "-"));  // 防止用户仍然不慎输入了中文。

                if (File.Exists(fullPath) == false)
                {
                    using (StreamWriter sw = File.CreateText(fullPath))
                    {
                        sw.Write($"\r\n%{FormatDocumentTitle(string.IsNullOrWhiteSpace(defaultTitle) ? fi.Name : defaultTitle)}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n");
                    }
                    return "";
                }
                else return "";
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        /// <summary>
        /// 调用 Windows Explorer（资源管理器）打开当前工作区中选定的条目所在的目录。
        /// </summary>
        private void miOpenFolder_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果是MD文件或图片，调用Explorer打开它所在的目录;
            //如果是目录本身，调用Explorer，直接打开该目录。
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            try
            {
                if (wtvi.IsMarkdownFilePath || wtvi.IsImageFileExist)
                {
                    //FileInfo fi = new FileInfo(wtvi.FullPath);
                    //System.Diagnostics.Process.Start("explorer.exe", "/select, " + $"\"{fi.Directory.FullName}\"");
                    System.Diagnostics.Process.Start("explorer.exe", "/select, " + $"\"{wtvi.FullPath}\"");
                }
                else if (wtvi.IsDirectoryExists)
                {
                    var metaFilePath = wtvi.MetaFilePath;
                    if (File.Exists(metaFilePath))
                    {
                        System.Diagnostics.Process.Start("explorer.exe", "/select, " + $"\"{metaFilePath}\"");
                    }
                    else
                    {
                        System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 根据当前工作区中选择的条目创建一个同级目录。
        /// </summary>
        private void miNewBrotherDirectory_Click(object sender, RoutedEventArgs e)
        {
            NewSameLevelDirectory();
        }

        internal void NewSameLevelDirectory()
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请在工作区中选择一个目录或Markdown文件再执行此操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
            if (di.Name.EndsWith("~"))
            {
                LMessageBox.Show("以波形符结尾的目录是程序自动管理的资源目录，不允许在其下新建子目录。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string parentDirectory = null;
            if (Directory.Exists(wtvi.FullPath))
            {
                parentDirectory = new DirectoryInfo(wtvi.FullPath).Parent.FullName;
            }
            else if (File.Exists(wtvi.FullPath))
            {
                if (wtvi.IsMarkdownFilePath)
                {
                    parentDirectory = new FileInfo(wtvi.FullPath).Directory.FullName;
                }
            }

            if (parentDirectory == null)
            {
                LMessageBox.Show("请在工作区中选择一个目录或Markdown文件再执行此操作。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var parentItem = wtvi.ParentWorkspaceTreeViewItem;

            if (wtvi.FullPath.ToLower() == Globals.PathOfWorkspace.ToLower())
            {
                LMessageBox.Show("不允许建立与工作区同级的子目录，新目录将会被包含在工作区目录中。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                parentDirectory = Globals.PathOfWorkspace;
                parentItem = wtvi;//工作区目录比较特殊
            }

            var newShortDirectoryName = InputBox.Show(Globals.AppName, $"◆ {wtvi.Title}\r\n◇ ..新目录..\r\n\r\n　 请输入新【同级】目录名（不能以“_”开头）：", "", true);
            if (string.IsNullOrEmpty(newShortDirectoryName))
            {
                //LMessageBox.Show("目录名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var newShortCommentName = newShortDirectoryName;

            newShortDirectoryName = ChinesePinYin.ToChinesePinYinText(newShortCommentName);

            if (AskEnglishFileName)
            {
                var regValidFileNameChars = new Regex(@"^[a-zA-Z0-9_][a-zA-Z0-9\-~_]{0,}");
                if (regValidFileNameChars.IsMatch(newShortCommentName) == false)
                {
                    // 为什么要用英文文件名？
                    // 这是因为 CHM 对中文路径的支持有问题——很多网络设备对中文路径的支持也有问题。
                    newShortDirectoryName = InputBox.Show(Globals.AppName, $"您输入的【{newShortCommentName}】将被用作目录元文件的标题。\r\n\r\n由于含有中文或特殊字符，建议使用下列字符串作目录名：",
                        newShortDirectoryName, true, "说明：\r\n　　⑴建议的文件名是取中文拼音首字母生成的。\r\n　　⑵您可以在这里改成有意义的英文字符串。\r\n　　⑶空白字符会被替换为连字符。", false, true);
                    if (string.IsNullOrWhiteSpace(newShortDirectoryName)) return;  // 用户放弃新建目录。
                }
            }

            newShortDirectoryName = ChinesePinYin.ToChinesePinYinText(newShortDirectoryName.Replace(" ", "-").Replace("　", "-").Replace("\t", "-"));  // 防止用户仍然不慎输入了中文。

            try
            {
                var newFullPath = (parentDirectory.EndsWith("\\") ? parentDirectory : (parentDirectory + "\\")) + newShortDirectoryName;
                if (Directory.Exists(newFullPath) == false)
                {
                    Directory.CreateDirectory(newFullPath);
                    var destDirectoryInfo = new DirectoryInfo(newFullPath);

                    //自动创建目录元文件，不必再双击了。

                    Regex headerNumberRegex = new Regex(@"^[lL]\d{7,}[ 　-]");
                    var matchHeaderNumber = headerNumberRegex.Match(newShortCommentName);
                    if (matchHeaderNumber.Success)
                    {
                        newShortCommentName = newShortCommentName.Substring(matchHeaderNumber.Length);
                    }

                    using (var stream = File.CreateText(destDirectoryInfo.FullName + "\\" + "_" + destDirectoryInfo.Name + ".md"))
                    {
                        stream.Write($"\r\n%{/*FormatDocumentTitle(*/newShortCommentName/*)*/}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n{Properties.Resources.metaFileInlineEnviromentTextSpan}\r\n\r\n");
                    }
                }
                else
                {
                    LMessageBox.Show("已存在指定名称的目录，操作无法完成。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                var newItem = new WorkspaceTreeViewItem(newFullPath, Globals.MainWindow);
                if (parentItem != null)
                {
                    //parentItem.Items.Add(newItem);

                    //2017年7月21日，现在已不必再排序，由用户决定。
                    //添加同级节点时，在当前节点下方，添加下级时，直接添加到下级最后一个。
                    #region 废弃代码
                    //List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                    //int baseIndex = 0;
                    //foreach (var item in parentItem.Items)
                    //{
                    //    var wi = item as WorkspaceTreeViewItem;
                    //    if (wi != null)
                    //    {
                    //        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                    //        {
                    //            baseIndex++;
                    //            continue;
                    //        }
                    //        tmpItems.Add(wi);
                    //    }
                    //}
                    //tmpItems.Add(newItem);
                    //tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                    //var index = tmpItems.IndexOf(newItem) + baseIndex;
                    //parentItem.Items.Insert(index, newItem); 
                    #endregion

                    var index = parentItem.Items.IndexOf(wtvi);
                    parentItem.Items.Insert(index + 1, newItem);
                    if (parentItem.IsExpanded == false) parentItem.IsExpanded = true;  //实际操作中应不可能出现需要这行的情形。
                }
                newItem.IsSelected = true;
                workspaceManager.SaveWorkspaceTreeviewToXml();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("创建目录失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);

            }
        }

        /// <summary>
        /// 查找任务列表。
        /// </summary>
        /// <param name="searchHeader">[-]表示未开始；[%]表示正在进行；[+]表示已完成；![+]表示未完成；[#]表示已废弃。</param>
        public void FindTaskList(string searchHeader, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            FindTaskList((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), searchHeader, treeView);

            if (treeView.Items.Count <= 0)
            {
                treeView.Items.Add(new TreeViewItem() { Header = "<没找到任务列表...>", });
            }

            cmbFindText.Items.Add(cmbFindText.Text);

            if (tcRightToolBar.SelectedItem != tiTaskList)
            {
                tcRightToolBar.SelectedItem = tiTaskList;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdRightToolsArea.Width = new GridLength(2, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 在磁盘文档或当前正在编辑的文档之间查找指定文本内容。
        /// </summary>
        /// <param name="searchArea">搜索的文档的范围（当前文档、打开的文档、全工作区）。</param>
        /// <param name="searchHeader">查找的任务项目的类型（[-]，未开始；[%]正在进行；[+]已完成；[#]已废弃。</param>
        private void FindTaskList(string searchArea, string searchHeader, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            switch (searchArea)
            {
                case "ActiveDocument":
                    {
                        FindTaskListInActiveDocument(searchHeader, treeView);
                        break;
                    }
                case "OpenedDocuments":
                    {
                        FindTaskListInOpenedDocuments(searchHeader, treeView);
                        break;
                    }
                case "AllFiles":
                    {
                        //这个需要用到递归
                        FindTaskListInAllFiles(Globals.PathOfWorkspace, searchHeader, treeView);
                        break;
                    }
                case "CheckedFiles":
                    {
                        //这个需要用到递归
                        FindTaskListInAllFiles(Globals.PathOfWorkspace, searchHeader, treeView, true);
                        break;
                    }
            }
        }

        /// <summary>
        /// 在打开的所有文档中查找任务列表。
        /// </summary>
        /// <param name="searchHeader">用以指定要查找的任务列表的类型。
        /// 应传入下列值之一：“[-]”、“[%]”、“[+]”、“![+]”、“[#]”、“”</param>
        /// <param name="treeView">指定将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInOpenedDocuments(string searchHeader, TreeView treeView)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    //为了排序，添加一个列表。
                    List<FindTaskListItem> tmpList = new List<FindTaskListItem>();

                    var lines = efi.EditorBase.Document.Lines;
                    for (int i = 1; i < lines.Count; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                    {
                        var line = lines[i];
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        if (string.IsNullOrEmpty(lineText)) continue;

                        var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                        bool isTaskListItem = false;
                        Brush foreColor = null;
                        if (tmp.StartsWith("[-]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Red;
                        }
                        else if (tmp.StartsWith("[%]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Green;
                        }
                        else if (tmp.StartsWith("[+]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Blue;
                        }
                        else if (tmp.StartsWith("[#]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Brown;
                        }

                        if (isTaskListItem)
                        {
                            bool isSearchItem = false;
                            if (string.IsNullOrEmpty(searchHeader))
                            {
                                isSearchItem = true;
                            }
                            else if (tmp.StartsWith(searchHeader))
                            {
                                isSearchItem = true;
                            }
                            else
                            {
                                if (searchHeader == "![+]")
                                {
                                    if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                    {
                                        isSearchItem = true;
                                    }
                                }
                            }

                            if (isSearchItem)
                            {
                                int preIndex, nextIndex;
                                preIndex = lineText.IndexOf('[') + 1;
                                nextIndex = lineText.IndexOf(']') - 1;
                                TextDecorationCollection textDecoration = null;
                                if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                                {
                                    textDecoration = TextDecorations.Strikethrough;
                                }

                                FindTaskListItem newItem;
                                if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                                {
                                    newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, preIndex, nextIndex, lineText, foreColor,
                                         FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                    tmpList.Add(newItem);
                                }
                                else
                                {
                                    newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, 0, nextIndex, lineText, foreColor,
                                         FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                    tmpList.Add(newItem);
                                }

                                for (int j = i + 1; j < lines.Count; j++)
                                {
                                    var line2 = lines[j];
                                    var line2Text = efi.EditorBase.Document.GetText(line2.Offset, line2.Length);
                                    if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                    if (CustomMarkdownSupport.IsDateLine(line2Text))
                                    {
                                        var leftIndex = line2Text.IndexOf("[");
                                        var rightIndex = line2Text.IndexOf("]");
                                        if (leftIndex >= 0 && rightIndex > leftIndex)
                                        {
                                            var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                                line2.LineNumber, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                            newItem.Items.Add(newDateItem);
                                        }
                                        else
                                        {
                                            var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                                line2.LineNumber, 0, 0, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                            newItem.Items.Add(newDateItem);
                                        }
                                    }
                                }

                                newItem.UpdateDateTime();
                                newItem.IsExpanded = true;
                            }
                        }
                    }

                    if (rbtnSortAsStartTime.IsChecked == true)
                    {
                        tmpList.Sort(new FindTaskListItemTimeStartCompare());
                    }
                    else if (rbtnSortAsEndTime.IsChecked == true)
                    {
                        tmpList.Sort(new FindTaskListItemTimeEndCompare());
                    }

                    foreach (var tmpItem in tmpList)
                    {
                        fdi.Items.Add(tmpItem);
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找任务列表。
        /// </summary>
        /// <param name="searchHeader">用以指定要查找的任务列表的类型。
        /// 应传入下列值之一：“[-]”、“[%]”、“[+]”、“![+]”、“[#]”、“”</param>
        /// <param name="treeView">指定将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInActiveDocument(string searchHeader, TreeView treeView)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                //为了排序，添加一个列表。
                List<FindTaskListItem> tmpList = new List<FindTaskListItem>();

                var lines = efi.EditorBase.Document.Lines;
                for (int i = 1; i < lines.Count; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                {
                    var line = lines[i];
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrEmpty(lineText)) continue;

                    var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                    bool isTaskListItem = false;
                    Brush foreground = null;
                    if (tmp.StartsWith("[-]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Red;
                    }
                    else if (tmp.StartsWith("[%]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Green;
                    }
                    else if (tmp.StartsWith("[+]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Blue;
                    }
                    else if (tmp.StartsWith("[#]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Brown;
                    }

                    if (isTaskListItem)
                    {
                        bool isSearchItem = false;
                        if (string.IsNullOrEmpty(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else if (tmp.StartsWith(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else
                        {
                            if (searchHeader == "![+]")
                            {
                                if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                {
                                    isSearchItem = true;
                                }
                            }
                        }

                        if (isSearchItem)
                        {
                            int preIndex, nextIndex;
                            preIndex = lineText.IndexOf('[') + 1;
                            nextIndex = lineText.IndexOf(']') - 1;
                            TextDecorationCollection textDecoration = null;
                            if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                            {
                                textDecoration = TextDecorations.Strikethrough;
                            }

                            FindTaskListItem newItem;
                            if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                            {
                                newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, preIndex, nextIndex, lineText, foreground,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }
                            else
                            {
                                newItem = new FindTaskListItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, 0, nextIndex, lineText, foreground,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }

                            for (int j = i + 1; j < lines.Count; j++)
                            {
                                var line2 = lines[j];
                                var line2Text = efi.EditorBase.Document.GetText(line2.Offset, line2.Length);
                                if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                if (CustomMarkdownSupport.IsDateLine(line2Text))
                                {
                                    var leftIndex = line2Text.IndexOf("[");
                                    var rightIndex = line2Text.IndexOf("]");
                                    if (leftIndex >= 0 && rightIndex > leftIndex)
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                            line2.LineNumber, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreground, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                    else
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(efi.FullFilePath, efi.ShortFileName,
                                            line2.LineNumber, 0, 0, line2Text, foreground, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                }
                            }

                            newItem.UpdateDateTime();
                            newItem.IsExpanded = true;
                        }
                    }
                }

                if (rbtnSortAsStartTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeStartCompare());
                }
                else if (rbtnSortAsEndTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeEndCompare());
                }

                foreach (var tmpItem in tmpList)
                {
                    fdi.Items.Add(tmpItem);
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// 根据指定的文件路径查找当前有没有哪个编辑器正在编辑这个文件。
        /// </summary>
        /// <param name="mdFileFullName">Markdown 文件的磁盘路径。</param>
        /// <returns>返回正在编辑指定 Markdown 文件的编辑器。</returns>
        private MarkdownEditor GetOpenedEditor(string mdFileFullName)
        {
            if (string.IsNullOrWhiteSpace(mdFileFullName)) return null;

            if (this.mainTabControl.Items.Count > 0)
            {
                foreach (var ue in this.mainTabControl.Items)
                {
                    var editor = ue as MarkdownEditor;
                    if (editor == null) continue;
                    if (editor.FullFilePath == null) continue;

                    if (editor.FullFilePath.ToLower() == mdFileFullName.ToLower()) return editor;
                }
            }

            return null;
        }

        /// <summary>
        /// 递归查找指定目录下的所有 Markdown 文件中定义的所有任务列表项，将将查找结果显示在指定的树型框中。
        /// </summary>
        /// <param name="directoryPath">要查找的目录</param>
        /// <param name="searchHeader">要查找的任务列表的类型（[-]为未开始；[%]为正在进行；[+]是已完成；[#]是已废弃。）</param>
        /// <param name="treeView">指定要将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInAllFiles(string directoryPath, string searchHeader, TreeView treeView, bool onlyCheckedFiles = false)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directory = new DirectoryInfo(directoryPath);

            //先处理当前目录下的文件
            var childFilesInfos = directory.GetFiles();

            foreach (var childFileInfo in childFilesInfos)
            {
                if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                if (onlyCheckedFiles)
                {
                    var wtvi = FindWorkspaceTreeViewItem(childFileInfo.FullName);
                    if (wtvi == null || wtvi.IsChecked != true) continue;
                }

                string[] lines;

                //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                if (fileEditor != null)
                {
                    lines = fileEditor.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' });
                }
                else
                {
                    lines = File.ReadAllLines(childFileInfo.FullName);
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);
                //为了排序，添加一个列表。
                List<FindTaskListItem> tmpList = new List<FindTaskListItem>();

                var lineNum = 1;   //跳过第1行，这里就必须从1开始。
                for (int i = 1; i < lines.Length; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                {
                    lineNum++;

                    var lineText = lines[i];

                    if (string.IsNullOrEmpty(lineText)) continue;

                    var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                    bool isTaskListItem = false;
                    Brush foreColor = null;
                    if (tmp.StartsWith("[-]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Red;
                    }
                    else if (tmp.StartsWith("[%]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Green;
                    }
                    else if (tmp.StartsWith("[+]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Blue;
                    }
                    else if (tmp.StartsWith("[#]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Brown;
                    }

                    if (isTaskListItem)
                    {
                        bool isSearchItem = false;
                        if (string.IsNullOrEmpty(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else if (tmp.StartsWith(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else
                        {
                            if (searchHeader == "![+]")
                            {
                                if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                {
                                    isSearchItem = true;
                                }
                            }
                        }

                        if (isSearchItem)
                        {
                            int preIndex, nextIndex;
                            preIndex = lineText.IndexOf('[') + 1;
                            nextIndex = lineText.IndexOf(']') - 1;
                            TextDecorationCollection textDecoration = null;
                            if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                            {
                                textDecoration = TextDecorations.Strikethrough;
                            }

                            FindTaskListItem newItem;
                            if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                            {
                                newItem = new FindTaskListItem(childFileInfo.FullName, childFileInfo.Name, lineNum, preIndex, nextIndex, lineText, foreColor,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }
                            else
                            {
                                newItem = new FindTaskListItem(childFileInfo.FullName, childFileInfo.Name, lineNum, 0, nextIndex, lineText, foreColor,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                tmpList.Add(newItem);
                            }

                            for (int j = i + 1; j < lines.Length; j++)
                            {
                                var line2Text = lines[j];
                                if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                if (CustomMarkdownSupport.IsDateLine(line2Text))
                                {
                                    var leftIndex = line2Text.IndexOf("[");
                                    var rightIndex = line2Text.IndexOf("]");
                                    if (leftIndex >= 0 && rightIndex > leftIndex)
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(childFileInfo.FullName, childFileInfo.Name,
                                            j + 1, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                    else
                                    {
                                        var newDateItem = new FindTaskListTimeTagItem(childFileInfo.FullName, childFileInfo.Name,
                                            j + 1, 0, 0, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                }
                            }

                            newItem.UpdateDateTime();
                            newItem.IsExpanded = true;
                        }
                    }
                }

                if (rbtnSortAsStartTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeStartCompare());
                }
                else if (rbtnSortAsEndTime.IsChecked == true)
                {
                    tmpList.Sort(new FindTaskListItemTimeEndCompare());
                }

                foreach (var tmpItem in tmpList)
                {
                    fdi.Items.Add(tmpItem);
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }

            //再递归处理子目录下的文件
            var childDirectories = directory.GetDirectories();
            foreach (var childDirectory in childDirectories)
            {
                FindTaskListInAllFiles(childDirectory.FullName, searchHeader, treeView);
            }
        }

        /// <summary>
        /// 按次序切换活动文档当前选中行中任务列表或时间标记的“任务完成状态”标记。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miSwitchTaskListItemOrDateTimeLineState_Click(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchTaskListItemOrDateTimeLineState(false, false);
        }

        /// <summary>
        /// 在当前活动编辑器当前选中行前面添加“无序列表”标记。
        /// </summary>
        private void miSetAsListItem_Click(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchListMark(true);
        }

        /// <summary>
        /// 删除当前活动编辑器当前选中行前面的“无序列表”标记。
        /// </summary>
        private void miDeleteListItemMark_Click(object sender, RoutedEventArgs e)
        {
            var editor = this.ActivedEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchListMark(false);
        }

        private void MiOpenNewWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedItem == null)
            {
                LMessageBox.Show("请先选定一个历史工作区记录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = lbxHistoryWorkspaces.SelectedItem as RecentDirectoryListBoxItem;
            if (item == null) return;

            var newWorkspacePath = item.DirectoryPath;
            if (newWorkspacePath.EndsWith("\\") == false)
                newWorkspacePath += "\\";
            var currentWorkspacePath = Globals.PathOfWorkspace;
            if (currentWorkspacePath.EndsWith("\\") == false)
                currentWorkspacePath += "\\";

            if (currentWorkspacePath.ToLower() == newWorkspacePath.ToLower())
            {
                LMessageBox.ShowWarning("别重复打开同一个工作区。");
                return;
            }

            if (File.Exists(newWorkspacePath + "~.editing"))
            {
                var answer = LMessageBox.Show("此工作区貌似已经打开，或者上次打开后未正常关闭。重复打开同一个工作区可能造成数据混乱。\r\n\r\n　　真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (answer != MessageBoxResult.Yes)
                    return;
            }

            if (Directory.Exists(item.DirectoryPath) == false)
            {
                LMessageBox.ShowWarning("磁盘上找不到这个工作区目录，无法继续。该目录可能已经被删除或移动。");
                return;
            }

            string exeFullName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
            try
            {
                if (newWorkspacePath.IndexOf(' ') >= 0)
                {
                    newWorkspacePath = "\"" + newWorkspacePath + "\"";
                }
                System.Diagnostics.Process.Start(exeFullName, newWorkspacePath);
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 将当前选定的“历史工作区目录”中记录的目录设置为当前工作区目录。
        /// </summary>
        private void miSetAsCurrentWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedItem == null)
            {
                LMessageBox.Show("请先选定一个历史工作区记录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = lbxHistoryWorkspaces.SelectedItem as RecentDirectoryListBoxItem;
            if (item == null) return;

            if (Directory.Exists(item.DirectoryPath) == false)
            {
                LMessageBox.ShowWarning("磁盘上找不到这个工作区目录，无法继续。该目录可能已经被删除或移动。");
                return;
            }

            if (Globals.PathOfWorkspace.ToLower() == item.DirectoryPath.ToLower())
            {
                return;
            }

            ChangeWorkspace(item.DirectoryPath);
        }

        private void miOpenCompiledChmFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedItem == null)
            {
                LMessageBox.Show("请先选定一个历史工作区记录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = lbxHistoryWorkspaces.SelectedItem as RecentDirectoryListBoxItem;
            if (item == null) return;

            if (Directory.Exists(item.DirectoryPath) == false)
            {
                LMessageBox.ShowWarning("磁盘上找不到这个工作区目录，无法继续。该目录可能已经被删除或移动。");
                return;
            }

            try
            {
                var di = new DirectoryInfo(item.DirectoryPath);
                var chmPath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + di.Name + ".chm";
                if (File.Exists(chmPath) == false)
                {
                    LMessageBox.Show($"未找到该工作区中已编译的名为【{di.Name + ".chm"}】的 CHM 的文档。\r\n　　请考虑【是否已经编译过】或【已更改过文件名】等情况。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (chmPath.Contains(" "))
                {
                    System.Diagnostics.Process.Start("explorer.exe", "\"" + chmPath + "\"");
                }
                else
                {
                    System.Diagnostics.Process.Start("explorer.exe", chmPath);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void miOpenInExplorer_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedItem == null)
            {
                LMessageBox.Show("请先选定一个历史工作区记录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = lbxHistoryWorkspaces.SelectedItem as RecentDirectoryListBoxItem;
            if (item == null) return;

            if (Directory.Exists(item.DirectoryPath) == false)
            {
                LMessageBox.Show("在指定位置找不到该目录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (item.DirectoryPath.Contains(" "))
            {
                System.Diagnostics.Process.Start("explorer.exe", "\"" + item.DirectoryPath + "\"");
            }
            else
            {
                System.Diagnostics.Process.Start("explorer.exe", item.DirectoryPath);
            }
        }

        #region 最小化到托盘图标

        /// <summary>
        /// 用于点击托盘图标时切换窗口状态。
        /// </summary>
        WindowState windowState;

        /// <summary>
        /// 托盘图标对象。
        /// </summary>
        System.Windows.Forms.NotifyIcon notifyIcon;

        /// <summary>
        /// 创建 Windows 系统托盘图标。
        /// </summary>
        private void CreateNoticeIcon()
        {
            string iconPath;
            if (Globals.InstalledPath.EndsWith("\\"))
            {
                iconPath = Globals.InstalledPath + "App.ico";
            }
            else
            {
                iconPath = Globals.InstalledPath + "\\App.ico";
            }

            this.notifyIcon = new System.Windows.Forms.NotifyIcon();
            this.notifyIcon.BalloonTipText = Globals.AppName; //设置程序启动时显示的文本
            this.notifyIcon.Text = Globals.AppName;//最小化到托盘时，鼠标点击时显示的文本
            this.notifyIcon.Icon = new System.Drawing.Icon(iconPath);//程序图标
            this.notifyIcon.Visible = true;
            notifyIcon.MouseDoubleClick += OnNotifyIconDoubleClick;
            notifyIcon.MouseClick += NotifyIcon_MouseClick;
            //this.notifyIcon.ShowBalloonTip(1000);
        }

        /// <summary>
        /// 托盘图标的快捷菜单。
        /// </summary>
        private ContextMenu iconContextMenu = null;

        /// <summary>
        /// 点击托盘图标时弹出快捷菜单。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NotifyIcon_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (iconContextMenu == null)
            {
                //ControlTemplate SubMenuItemControlTemplate = Globals.MainWindow.TryFindResource("SubMenuItemControlTemplate") as ControlTemplate;

                iconContextMenu = new ContextMenu()
                {
                    FontFamily = this.FontFamily,
                    FontSize = 14,
                    Style = TryFindResource("MetroContextMenu") as Style,
                };
                TextOptions.SetTextFormattingMode(iconContextMenu, TextFormattingMode.Display);
                MenuItem miShowWindow = new MenuItem()
                {
                    Header = "显示窗口(_S)",
                    Style = TryFindResource("MetroMenuItem") as Style,
                };
                miShowWindow.Click += MiShowWindow_Click;

                MenuItem miExit = new MenuItem()
                {
                    Header = "退出(_X)",
                    Style = TryFindResource("MetroMenuItem") as Style,
                };
                miExit.Click += MiExit_Click;

                this.iconContextMenu.Items.Add(miShowWindow);
                this.iconContextMenu.Items.Add(miExit);
            }

            iconContextMenu.IsOpen = true;
        }

        /// <summary>
        /// 点击托盘图标快捷菜单中的“退出程序”，关闭程序。
        /// </summary>
        private void MiExit_Click(object sender, RoutedEventArgs e)
        {
            //App.Current.Shutdown();//注意，这能使用这个
            this.isForceExit = true;//强行忽略这个字段的值。
            this.Close();
        }

        /// <summary>
        /// 点击托盘图标中的“显示窗口”，使程序主窗口显示出来。
        /// </summary>
        private void MiShowWindow_Click(object sender, RoutedEventArgs e)
        {
            this.Show();
            WindowState = windowState;
        }

        /// <summary>
        /// 双击托盘图标显示程序主窗口。
        /// </summary>
        private void OnNotifyIconDoubleClick(object sender, EventArgs e)
        {
            this.Show();
            WindowState = windowState;
        }

        private bool isCloseToIcon = false;
        /// <summary>
        /// 关闭到托盘图标。
        /// </summary>
        public bool IsCloseToIcon
        {
            get
            {
                return isCloseToIcon;
            }
        }

        private bool isPopupContextToolbarEnabled = true;
        /// <summary>
        /// 是否在编辑时自动弹出上下文快捷工具栏。
        /// </summary>
        public bool IsPopupContextToolbarEnabled
        {
            get { return isPopupContextToolbarEnabled; }
            set
            {
                this.isPopupContextToolbarEnabled = value;
                RefreshIsPopupContextToolbarEnabled(value);
            }
        }

        private void RefreshIsPopupContextToolbarEnabled(bool value)
        {
            miIsPopupContextToolbarEnabled.IsChecked = value;
            if (value)
            {
                btnSwitchPopupToolbarEnabled.ToolTip = "已开启快捷工具栏";
                backgroundGridIsPopupToolbarEnabled.Background = Brushes.DarkCyan;
            }
            else
            {
                btnSwitchPopupToolbarEnabled.ToolTip = "已关闭快捷工具栏";
                backgroundGridIsPopupToolbarEnabled.Background = Brushes.Transparent;
            }
        }

        /// <summary>
        /// 是否在编译工作区时忽略被加密的文件，否则每次都会要求输入密码。
        /// 这个选项默认打开，因为加密文件生成CHM工程文件时无法读取名称。
        /// </summary>
        public bool IgnoreEncryptedFiles { get; private set; }

        /// <summary>
        /// 是否在编译工作区时忽略被标记为“废弃”的文件。此特性默认打开。
        /// </summary>
        public bool IgnoreAbortedFiles { get; private set; } = true;

        /// <summary>
        /// 忽略isCloseToIcon的值，强制退出。
        /// </summary>
        private bool isForceExit = false;

        /// <summary>
        /// 开关“CloseToIcon”属性。此属性决定点击窗口右上角“关闭”按钮时是退出程序还是隐藏到托盘图标。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miCloseToIcon_Click(object sender, RoutedEventArgs e)
        {
            isCloseToIcon = miCloseToIcon.IsChecked = !miCloseToIcon.IsChecked;

            App.ConfigManager.Set("CloseToIcon", isCloseToIcon.ToString());
        }
        #endregion

        /// <summary>
        /// 在工作区管理器中选定的目录位置查找该目录下所有 Markdown 文件中定义的锚。
        /// </summary>
        private void miSearchAnchorsInDirectory_Click(object sender, RoutedEventArgs e)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区管理器中选择一个目录作为查找目标！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.IsDirectoryExists == false)
            {
                LMessageBox.Show("请选择目录而不是其它项目！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show("以波型符结尾的目录是资源目录，不允许执行此操作！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            //锚的定义方式：
            //[锚名](@锚ID)
            //锚名可以省略，但锚ID不能省略。
            //编译后，会变成这样：
            //<a id="锚ID">锚名</a>
            FindTextInAllFiles(wtvi.FullPath, @"\[.*\]\(@.*\)", true, tvFindAndReplace);

            if (tcRightToolBar.SelectedItem != tiFindResult)
            {
                tcRightToolBar.SelectedItem = tiFindResult;
            }

            if (cdRightToolsArea.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }

        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有指定类型任务列表项。
        /// </summary>
        /// <param name="headerKey">要查找什么类型的任务列表，需要传入“[-]”、“[+]”、“![+]”、“[#]”、“”、“[%]”这几种之一。</param>
        /// <param name="treeView">指定要将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInDirectory(string headerKey, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();

            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区管理器中选择一个目录作为查找目标！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.IsDirectoryExists == false)
            {
                LMessageBox.Show("请选择目录而不是其它项目！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show("以波型符结尾的目录是资源目录，不允许执行此操作！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            FindTaskListInAllFiles(wtvi.FullPath, headerKey, treeView);

            if (tvTaskList.Items.Count <= 0)
            {
                tvTaskList.Items.Add("<没找到结果...>");
            }

            if (tcRightToolBar.SelectedItem != tiTaskList)
            {
                tcRightToolBar.SelectedItem = tiTaskList;
            }

            if (cdRightToolsArea.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“未开始”的任务列表项。
        /// </summary>
        private void miSearchUnStartTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[-]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“未完成”的任务列表项。
        /// </summary>
        private void miSearchUnFinishedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("![+]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“正在进行”的任务列表项。
        /// </summary>
        private void miSearchProcessingTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[%]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“废弃”的任务列表项。
        /// </summary>
        private void miSearchAbortedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[#]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“已完成”的任务列表项。
        /// </summary>
        private void miSearchFinishedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[+]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有任务列表项。
        /// </summary>
        private void miSearchAllTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“未开始”的任务列表项。
        /// </summary>
        private void miSearchUnStartTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[-]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“正在进行”的任务列表项。
        /// </summary>
        private void miSearchProcessingTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[%]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“废弃”的任务列表项。
        /// </summary>
        private void miSearchAbortedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[#]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“已完成”的任务列表项。
        /// </summary>
        private void miSearchFinishedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[+]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有任务列表项。
        /// </summary>
        private void miSearchAllTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有任务列表项。
        /// </summary>
        private void btnFindTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“未完成”的任务列表项。
        /// </summary>
        private void miSearchUnFinishedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("![+]", tvTaskList);
        }

        /// <summary>
        /// 打开“查找/替换”面板，准备在当前文档中查找当前活动编辑器中选定的文本。
        /// 打开面板后立即查找第一个符合条件的文本片段。
        /// 如果当前没有打开任何文档，就准备在全工作区中查找。
        /// </summary>
        private void miFindInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            if (rdFindAndReplace.ActualHeight <= 40)
            {
                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                cmbFindText.UpdateLayout();
            }

            var editor = ActivedEditor;
            if (editor == null)
            {
                cmbSearchArea.SelectedIndex = 2;//没有打开任何文档，查找全工作区。
                return;
            }

            if (editor.EditorBase.SelectionLength > 0)
            {
                cmbFindText.Text = editor.EditorBase.SelectedText;
            }

            cmbSearchArea.SelectedIndex = 0;

            cmbFindText.Focus();
            if (cmbFindText.Text.Length > 0)
            {
                btnFind_Click(sender, e);
            }

            //独立窗口效果不好，整合到主界面上。
            //var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            //if (activeEditor != null)
            //{
            //    FindReplaceDialog.ShowForFind(activeEditor.EditorBase, this);
            //}
        }

        /// <summary>
        /// 打开“查找/替换”面板，准备在当前文档中查找并替换文本。
        /// 如果当前没有打开任何文档，就准备在全工作区中查找并替换。
        /// </summary>
        private void miFindAndReplaceInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            //打开查找替换面板
            rdFindAndReplace.Height = new GridLength(0, GridUnitType.Auto);
            cmbFindText.UpdateLayout();

            var editor = ActivedEditor;
            if (editor == null)
            {
                cmbSearchArea.SelectedIndex = 2;//没有打开任何文档，查找全工作区。
                return;
            }

            if (editor.EditorBase.SelectionLength > 0)
            {
                cmbFindText.Text = editor.EditorBase.SelectedText;
            }

            cmbSearchArea.SelectedIndex = 0;

            if (cmbFindText.Text.Length > 0)
            {
                cmbReplaceTextInputBox.Focus();
            }

            //var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            //if (activeEditor != null)
            //{
            //    FindReplaceDialog.ShowForReplace(activeEditor.EditorBase, this);
            //}
        }

        /// <summary>
        /// 在当前活动编辑器的当前行插入一个时间标签文本（形如： [2016-05-13 10:00] xxx)。
        /// 如果当前行本身就是时间标签，则将其时间更改为当前时间。
        /// </summary>
        private void miInsertDate_Click(object sender, RoutedEventArgs e)
        {
            InsertDateText();
        }

        /// <summary>
        /// 从指定的时间生成格式化的日期文本。格式化后的时间类似：2016-05-13。
        /// 这样做的目的是保证每个时间标签的长度一致。
        /// </summary>
        /// <param name="datetime"></param>
        /// <returns></returns>
        public static string FormatDateText(DateTime datetime)
        {
            var srcText = datetime.Date.ToShortDateString();
            var spans = srcText.Split(new char[] { '-', '.', ',', '，', '。', '－', '、', '．', '/' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < spans.Length; i++)
            {
                var span = spans[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb.Append(span + "-");
            }

            var result = sb.ToString();
            if (result.EndsWith("-")) return result.Substring(0, result.Length - 1);

            return result;
        }

        /// <summary>
        /// 从指定的时间生成格式化的日期、时间文本。格式化后的时间类似：2016-05-13 10:01。
        /// 这样做的目的是保证每个时间标签的长度一致。
        /// </summary>
        public static string FormatDateTimeText(DateTime datetime)
        {
            var srcText = datetime.Date.ToShortDateString();
            var spans = srcText.Split(new char[] { '-', '.', ',', '，', '。', '－', '、', '．', '/' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < spans.Length; i++)
            {
                var span = spans[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb.Append(span + "-");
            }

            var result = sb.ToString();
            if (result.EndsWith("-")) result = result.Substring(0, result.Length - 1);

            var timeText = datetime.ToShortTimeString();
            StringBuilder sb2 = new StringBuilder();
            var spans2 = timeText.Split(new char[] { ':' });
            for (int i = 0; i < spans2.Length; i++)
            {
                var span = spans2[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb2.Append(span + ":");
            }
            var result2 = sb2.ToString();
            result += " " + result2.Substring(0, result2.Length - 1);

            return result;
        }

        /// <summary>
        /// 在当前活动编辑器的当前行插入一个时间标签文本（形如： [2016-05-13 10:00] xxx)。
        /// 如果当前行本身就是时间标签，则将其时间更改为当前时间。
        /// </summary>
        private void InsertDateText()
        {
            var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (activeEditor == null) return;

            var now = DateTime.Now;
            var header = "";
            if (activeEditor.EditorBase.Document.GetLineByOffset(activeEditor.EditorBase.SelectionStart).Offset == activeEditor.EditorBase.SelectionStart)
            {
                header = " ";//自动格式化一下
            }

            var line = activeEditor.EditorBase.Document.GetLineByOffset(activeEditor.EditorBase.SelectionStart);
            var lineText = activeEditor.EditorBase.Document.GetText(line.Offset, line.Length);
            if (CustomMarkdownSupport.IsTaskLine(lineText))
            {
                if (line.LineNumber == 1)
                {
                    activeEditor.EditorBase.BeginChange();
                    activeEditor.EditorBase.SelectedText =
                        now.Year + "年" + now.Month + "月" + now.Day + "日 " +
                        now.Hour + "时" + now.Minute + "分" + now.Second + "秒";
                    activeEditor.EditorBase.EndChange();
                    return;
                }

                //在最后插入一个新行（2016年5月26日废弃此方案）
                #region 废弃，这个方案不好——如果任务列表中项目过多，可能导致难以查看
                //DocumentLine lastDateLine = null;
                //for (int i = line.LineNumber + 1; i <= activeEditor.EditorBase.Document.LineCount; i++)//LineNumber从１开始。
                //{
                //    var l = activeEditor.EditorBase.Document.GetLineByNumber(i);
                //    var lt = activeEditor.EditorBase.Document.GetText(l.Offset, l.Length);

                //    if (CustomMarkdownSupport.IsDateLine(lt))
                //    {
                //        lastDateLine = l;
                //    }
                //    else if (CustomMarkdownSupport.IsTaskLine(lt) || lt.StartsWith("#"))
                //    {
                //        if (i - 2 > line.LineNumber)
                //        {
                //            lastDateLine = activeEditor.EditorBase.Document.GetLineByNumber(i - 2);
                //        }
                //        else if (i - 1 > line.LineNumber)
                //        {
                //            lastDateLine = activeEditor.EditorBase.Document.GetLineByNumber(i - 2);
                //        }
                //        break;
                //    }
                //}

                //if (lastDateLine == null) lastDateLine = line;//如果找不到，就直接在当前行后面添加。 
                #endregion

                var markText = CustomMarkdownSupport.GetHeaderOfTaskListItem(lineText);
                if (markText.Contains("-")) markText = "[-]";
                else if (markText.Contains("%")) markText = "[%]";
                else if (markText.Contains("#")) markText = "[#]";
                else if (markText.Contains("+")) markText = "[+]";
                else markText = "";

                header = " ";

                var newDateLine = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}]{markText} ";
                activeEditor.EditorBase.Document.Insert(/*lastDateLine*/line.EndOffset, $"\r\n\r\n{newDateLine}");
                var destSel = /*lastDateLine*/line.EndOffset + 4 + newDateLine.Length;
                if (destSel < activeEditor.EditorBase.Document.TextLength)
                {
                    activeEditor.EditorBase.Select(destSel, 0);
                    activeEditor.EditorBase.ScrollToLine(/*lastDateLine*/line.LineNumber + 2);
                }
            }
            else if (CustomMarkdownSupport.IsDateLine(lineText))
            {
                //修改当前行的时间
                var indexStart = lineText.IndexOf("[");
                var indexEnd = lineText.IndexOf("]");
                if (indexStart >= 0 && indexEnd >= 0)
                {
                    //修改当前时间
                    var newLineText = lineText.Substring(0, indexStart + 1) + FormatDateText(DateTime.Now) +
                       $" {now.ToShortTimeString()}" + lineText.Substring(indexEnd);
                    activeEditor.EditorBase.Document.Replace(line.Offset, line.Length, newLineText);
                }
                else
                {
                    //在当前位置插入时间
                    activeEditor.EditorBase.SelectedText = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}] ";
                    var destSel = activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                    if (destSel < activeEditor.EditorBase.Document.TextLength)
                    {
                        activeEditor.EditorBase.Select(destSel, 0);
                    }
                }
            }
            else
            {
                //在当前位置插入时间

                activeEditor.EditorBase.SelectedText = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}] ";
                var destSel = activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                if (destSel < activeEditor.EditorBase.Document.TextLength)
                {
                    activeEditor.EditorBase.Select(destSel, 0);
                }
            }
        }

        /// <summary>
        /// 开关“IgnoreEncryptedFile”选项。此选项决定在编译工作区时是否跳过被加密的文件。
        /// 如此选项被关闭，则编译被加密的每个文件时都有可能要求用户输入一次密码。
        /// </summary>
        private void miIgnoreEncryptedFile_Click(object sender, RoutedEventArgs e)
        {
            this.IgnoreEncryptedFiles =
            miIgnoreEncryptedFile.IsChecked = !miIgnoreEncryptedFile.IsChecked;
            App.WorkspaceConfigManager.Set("IgnoreEncryptedFiles", miIgnoreEncryptedFile.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "IgnoreEncryptedFiles",
                HtmlOptionText = "忽略加密文件",
            });
        }

        /// <summary>
        /// 开关“IgnoreAbortedFile”选项。此选项决定在编译工作区时是否跳过被标记为“废弃”的文件。
        /// （即第一行为“[#] xxx”的Markdown文件。
        /// </summary>
        private void miIgnoreAbortedFile_Click(object sender, RoutedEventArgs e)
        {
            this.IgnoreAbortedFiles =
            miIgnoreAbortedFile.IsChecked = !miIgnoreAbortedFile.IsChecked;
            App.WorkspaceConfigManager.Set("IgnoreAbortedFile", miIgnoreAbortedFile.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "IgnoreAbortedFile",
                HtmlOptionText = "忽略加废弃标记的文件",
            });
        }

        /// <summary>
        /// 开关“HideExamAnswer”选项。此选项用于在编译 Html 时决定编译后的页面中试题的答案是否直接显示。
        /// 若此选项开启，则不显示答案；若关闭此选项，则编译后的 Html 中直接显示答案。
        /// </summary>
        private void miHideExamAnswer_Click(object sender, RoutedEventArgs e)
        {
            this.hideExamAnswer =
            miHideExamAnswer.IsChecked = !miHideExamAnswer.IsChecked;
            App.WorkspaceConfigManager.Set("HideExamAnswer", miHideExamAnswer.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "HideExamAnswer",
                HtmlOptionText = "隐藏试题答案与解析",
            });
        }

        /// <summary>
        /// 关闭“查找/替换”面板。
        /// </summary>
        private void btnCloseFindAndReplacePanel_Click(object sender, RoutedEventArgs e)
        {
            rdFindAndReplace.Height = new GridLength(0);
            if (ActivedEditor != null) FocusActiveEditor();
        }

        /// <summary>
        /// 激活当前活动编辑器（使之获得焦点）。
        /// </summary>
        public void FocusActiveEditor()
        {
            //重置焦点。
            this.mainTabControl.Focus();

            var efi = ActivedEditor;
            if (efi == null) return;

            efi.EditorBase.UpdateLayout();
            var destSel = efi.EditorBase.Text.Length;
            if (destSel < efi.EditorBase.Document.TextLength)
            {
                efi.EditorBase.Select(destSel, 0);
            }
            efi.UpdateLayout();
            efi.EditorBase.Focus();
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        private void btnFind_Click(object sender, RoutedEventArgs e)
        {
            Find();
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        public void Find()
        {
            if (cmbFindText.Text.Length <= 0) return;

            if (!FindNext(cmbFindText.Text))
                SystemSounds.Beep.Play();

            cmbFindText.Items.Insert(0, cmbFindText.Text);
        }

        /// <summary>
        /// 根据“查找/替换”面板中配置的选项，生成正则表达式对象。
        /// </summary>
        /// <param name="textToFind">要查找的文本。</param>
        /// <param name="leftToRight">是否顺序查找（从前向后、从后向前）</param>
        /// <param name="forceRegex">是否强制按正则表达式查找。</param>
        /// <returns>返回一个正则对象。</returns>
        private Regex GetRegEx(string textToFind, bool leftToRight, bool forceRegex)
        {
            RegexOptions options = RegexOptions.None;
            if (cbSearchUp.IsChecked == true && !leftToRight)
                options |= RegexOptions.RightToLeft;
            if (cbCaseSensitive.IsChecked == false)
                options |= RegexOptions.IgnoreCase;

            options |= RegexOptions.Multiline;

            if (cbRegex.IsChecked == true || forceRegex)
            {
                return new Regex(textToFind, options);
            }
            else
            {
                string pattern = Regex.Escape(textToFind);
                if (cbWildcards.IsChecked == true)
                {
                    pattern = pattern.Replace("\\*", ".*").Replace("\\?", ".");
                }

                Regex convert = new Regex(@"\\.");
                pattern = convert.Replace(pattern, new MatchEvaluator(ConvertEscapeChars));

                if (cbWholeWord.IsChecked == true)
                    pattern = "\\b" + pattern + "\\b";
                return new Regex(pattern, options);
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        private bool FindNext(string textToFind)
        {
            try
            {
                var activeEditor = ActivedEditor;
                if (activeEditor == null) return false;

                Regex regex = GetRegEx(textToFind, false, false);
                int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ?
                activeEditor.EditorBase.SelectionStart : activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                Match match = regex.Match(activeEditor.EditorBase.Text, start);

                if (!match.Success)  // start again from beginning or end
                {
                    if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                        match = regex.Match(activeEditor.EditorBase.Text, activeEditor.EditorBase.Text.Length);
                    else
                        match = regex.Match(activeEditor.EditorBase.Text, 0);
                }

                if (match.Success)
                {
                    var destSel = match.Index;
                    if (destSel < activeEditor.EditorBase.Document.TextLength)
                    {
                        activeEditor.EditorBase.Select(destSel, match.Length);
                    }
                    TextLocation loc = activeEditor.EditorBase.Document.GetLocation(match.Index);
                    activeEditor.EditorBase.ScrollTo(loc.Line, loc.Column);
                }

                return match.Success;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// 在当前活动编辑器中按指定条件顺序替换。
        /// </summary>
        private void btnReplace_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            if (cmbFindText.Text.Length <= 0)
            {
                LMessageBox.Show("请输入要查找文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                Regex regex = GetRegEx(cmbFindText.Text, false, false);
                string input = activeEditor.EditorBase.Text.Substring(activeEditor.EditorBase.SelectionStart, activeEditor.EditorBase.SelectionLength);
                Match match = regex.Match(input);
                bool replaced = false;
                if (match.Success && match.Index == 0 && match.Length == input.Length)
                {
                    var newTextForReplace = cmbReplaceTextInputBox.Text;

                    Regex convert1 = new Regex(@"\\.");
                    newTextForReplace = convert1.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                    Regex convert2 = new Regex(@"\\{1,2}x");
                    var matches = convert2.Matches(newTextForReplace);
                    for (int j = matches.Count - 1; j >= 0; j--)
                    {
                        var xMatch = matches[j];
                        if (xMatch.Value == @"\\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                        else if (xMatch.Value == @"\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                    }

                    activeEditor.EditorBase.Document.Replace(activeEditor.EditorBase.SelectionStart,
                    activeEditor.EditorBase.SelectionLength, newTextForReplace);
                    replaced = true;
                }

                if (!FindNext(cmbFindText.Text) && !replaced)
                    SystemSounds.Beep.Play();

                cmbFindText.Items.Insert(0, cmbFindText.Text);
                cmbReplaceTextInputBox.Items.Insert(0, cmbReplaceTextInputBox.Text);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 先取剪贴板中的文本，再针对这些文本执行替换操作，最后粘贴替换过的文本。
        /// </summary>
        private void btnReplaceAndPaste_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            if (cmbFindText.Text.Length <= 0)
            {
                LMessageBox.Show("请输入要查找文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var sourceText = Clipboard.GetText(TextDataFormat.UnicodeText);
            if (string.IsNullOrWhiteSpace(sourceText))
            {
                LMessageBox.Show("剪贴板中没有可供替换并粘贴的文本。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                Regex regex = GetRegEx(cmbFindText.Text, true, false);

                var matchs = regex.Matches(sourceText);
                int replacedCount = 0;
                for (int i = matchs.Count - 1; i >= 0; i--)
                {
                    var match = matchs[i];
                    var newTextForReplace = cmbReplaceTextInputBox.Text;

                    Regex convert = new Regex(@"\\.");
                    newTextForReplace = convert.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                    Regex convert2 = new Regex(@"\\{1,2}x");
                    var matches = convert2.Matches(newTextForReplace);
                    for (int j = matches.Count - 1; j >= 0; j--)
                    {
                        var xMatch = matches[j];
                        if (xMatch.Value == @"\\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                        else if (xMatch.Value == @"\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                    }

                    sourceText = sourceText.Substring(0, match.Index) + newTextForReplace + sourceText.Substring(match.Index + match.Length);
                    replacedCount++;
                }

                activeEditor.EditorBase.BeginChange();
                activeEditor.EditorBase.Document.Replace(activeEditor.EditorBase.SelectionStart, activeEditor.EditorBase.SelectionLength, sourceText);
                activeEditor.EditorBase.EndChange();

                var findText = cmbFindText.Text;
                for (int i = cmbFindText.Items.Count - 1; i >= 0; i--)
                {
                    if (cmbFindText.Items[i] as string == findText)
                    {
                        cmbFindText.Items.RemoveAt(i);
                    }
                }
                cmbFindText.Items.Insert(0, findText);

                var replaceText = cmbReplaceTextInputBox.Text;
                for (int i = cmbReplaceTextInputBox.Items.Count - 1; i >= 0; i--)
                {
                    if (cmbReplaceTextInputBox.Items[i] as string == replaceText)
                    {
                        cmbReplaceTextInputBox.Items.RemoveAt(i);
                    }
                }
                cmbReplaceTextInputBox.Items.Insert(0, replaceText);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在当前文档中按“查找”框中指定的关键词查找所有符合条件的结果，并全部替换为“替换”框中指定的文本。
        /// </summary>
        private void btnReplaceAll_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            if (cmbFindText.Text.Length <= 0)
            {
                LMessageBox.Show("请输入要查找文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var result = LMessageBox.Show("你确定把所有找到的 “" +
                                cmbFindText.Text + "” 替换成 “" + cmbReplaceTextInputBox.Text + "”吗？",
                                "Lunar Markdown Edit - 全部替换", MessageBoxButton.OKCancel, MessageBoxImage.Question);
            if (result == MessageBoxResult.OK)
            {
                try
                {
                    Regex regex = GetRegEx(cmbFindText.Text, true, false);
                    activeEditor.EditorBase.BeginChange();

                    var matchs = regex.Matches(activeEditor.EditorBase.Text);
                    int replacedCount = 0;
                    for (int i = matchs.Count - 1; i >= 0; i--)
                    {
                        var match = matchs[i];

                        if (cbReplaceOnlyInSelectionText.IsChecked == true)
                        {
                            if (match.Index + match.Length > activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength) continue;
                            if (match.Index < activeEditor.EditorBase.SelectionStart) break;//不需要再考虑index更小的情况了
                        }

                        var newTextForReplace = cmbReplaceTextInputBox.Text;

                        Regex convert = new Regex(@"\\.");
                        newTextForReplace = convert.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                        Regex convert2 = new Regex(@"\\{1,2}x");
                        var matches = convert2.Matches(newTextForReplace);
                        for (int j = matches.Count - 1; j >= 0; j--)
                        {
                            var xMatch = matches[j];
                            if (xMatch.Value == @"\\x")
                            {
                                newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                    newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                            }
                            else if (xMatch.Value == @"\x")
                            {
                                newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                    + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                            }
                        }

                        activeEditor.EditorBase.Document.Replace(match.Index, match.Length, newTextForReplace);
                        replacedCount++;
                    }
                    activeEditor.EditorBase.EndChange();

                    var findText = cmbFindText.Text;
                    for (int i = cmbFindText.Items.Count - 1; i >= 0; i--)
                    {
                        if (cmbFindText.Items[i] as string == findText)
                        {
                            cmbFindText.Items.RemoveAt(i);
                        }
                    }
                    cmbFindText.Items.Insert(0, findText);

                    var replaceText = cmbReplaceTextInputBox.Text;
                    for (int i = cmbReplaceTextInputBox.Items.Count - 1; i >= 0; i--)
                    {
                        if (cmbReplaceTextInputBox.Items[i] as string == replaceText)
                        {
                            cmbReplaceTextInputBox.Items.RemoveAt(i);
                        }
                    }
                    cmbReplaceTextInputBox.Items.Insert(0, replaceText);

                    if (cbReplaceOnlyInSelectionText.IsChecked == true)
                    {
                        LMessageBox.Show($"在当前文档的选定文本中替换了【{replacedCount}】处文本。", Globals.AppName,
                              MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else
                    {
                        LMessageBox.Show($"在当前文档中替换了【{replacedCount}】处文本。", Globals.AppName,
                              MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        /// <summary>
        /// 替换框中“\r”会被当作回车符、“\n”会被当作换行符、“\t”会被解释为Tab符、“\\”会被解释为单个“\”。
        /// 所以想要替换“\r”本身时，需要转义。
        /// </summary>
        /// <param name="m">正则表达式匹配项。</param>
        /// <returns>转义后的文本。</returns>
        static string ConvertEscapeChars(Match m)
        {
            string x = m.ToString();
            switch (m.Value)
            {
                case "\\\\": return "\\";
                case "\\r": return "\r";
                case "\\n": return "\n";
                case "\\t": return "\t";
                default: return m.Value;
            }
        }

        /// <summary>
        /// 刷新“查找/替换”面板上各部件的“可用/禁用”状态。避免用户误操作。
        /// </summary>
        private void cmbSearchArea_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            RefreshFindButtonsStatus();

            if (cmbSearchArea.SelectedItem != null)
            {
                var selItem = cmbSearchArea.SelectedItem as ComboBoxItem;
                if (selItem != null)
                {
                    var tag = selItem.Tag as string;
                    if (tag == "CheckedFiles")
                    {
                        if (ckxShowCheckBox.IsChecked != true)
                        {
                            ckxShowCheckBox.IsChecked = true;
                            WorkspaceCheckable = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 刷新“查找/替换”面板上各部件的“可用/禁用”状态。避免用户误操作。
        /// </summary>
        private void RefreshFindButtonsStatus()
        {
            if (btnFind == null) return;
            if (btnReplace == null) return;
            if (btnReplaceAll == null) return;
            if (cmbReplaceTextInputBox == null) return;
            if (cbCaseSensitive == null) return;
            if (cbWholeWord == null) return;
            if (cbRegex == null) return;
            if (cbWildcards == null) return;
            if (cbSearchUp == null) return;
            if (cbReplaceOnlyInSelectionText == null) return;

            switch (cmbSearchArea.SelectedIndex)
            {
                case 0:
                    {
                        btnFind.IsEnabled =
                            btnReplace.IsEnabled =
                            btnReplaceAll.IsEnabled =
                            cmbReplaceTextInputBox.IsEnabled =
                            cbSearchUp.IsEnabled =
                            cbReplaceOnlyInSelectionText.IsEnabled = true;

                        cbSearchUp.Foreground = Brushes.Black;
                        cbReplaceOnlyInSelectionText.Foreground = Brushes.Brown;

                        //cbCaseSensitive.IsEnabled =
                        //cbRegex.IsEnabled =
                        //cbSearchUp.IsEnabled =
                        //cbWholeWord.IsEnabled =
                        //cbWildcards.IsEnabled = true;
                        break;
                    }
                default:
                    {
                        btnFind.IsEnabled =
                            btnReplace.IsEnabled =
                            btnReplaceAll.IsEnabled =
                            cmbReplaceTextInputBox.IsEnabled =
                            cbSearchUp.IsEnabled =
                            cbReplaceOnlyInSelectionText.IsEnabled = false;

                        cbReplaceOnlyInSelectionText.IsChecked = false;

                        cbSearchUp.Foreground = Brushes.Gray;
                        cbReplaceOnlyInSelectionText.Foreground = Brushes.Gray;

                        //cbCaseSensitive.IsEnabled =
                        //cbRegex.IsEnabled =
                        //cbSearchUp.IsEnabled =
                        //cbWholeWord.IsEnabled =
                        //cbWildcards.IsEnabled = false;
                        break;
                    }
            }
        }

        /// <summary>
        /// 主界面“查找任务列表”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定类型的任务列表。
        /// 这两个按钮看起来就是个 SplitButton。此方法用以实现类似 SplitButton 的效果。
        /// </summary>
        private void btnFindTaskListItems_AppendArrow_Initialized(object sender, EventArgs e)
        {
            //SplitterButton的弹出菜单要处理一下，左键单显示，右键不显示
            splitMenu.Placement = PlacementMode.Bottom;
            splitMenu.PlacementTarget = btnFindTaskListItems_AppendArrow;

            btnFindTaskListItems_AppendArrow.ContextMenu = null;

            splitMenu.Opened += SplitMenu_Opened;
        }

        private void SplitMenu_Opened(object sender, RoutedEventArgs e)
        {
            //var point = btnFindTaskListItems_AppendArrow.PointToScreen(new Point(0, 0));
            if (Environment.OSVersion.Version.Major > 6 ||
                (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))  // win 8 以上（6.2）
            {
                splitMenu.HorizontalOffset = -splitMenu.ActualWidth + btnFindTaskListItems_AppendArrow.ActualWidth + 6;// point.X - splitMenu.ActualWidth + btnFindTaskListItems_AppendArrow.ActualWidth;
                splitMenu.VerticalOffset = 2;// point.Y - 8;
            }
            else
            {
                splitMenu.HorizontalOffset = -splitMenu.ActualWidth + btnFindTaskListItems_AppendArrow.ActualWidth;
                splitMenu.VerticalOffset = 2;// point.Y + btnFindTaskListItems_AppendArrow.ActualHeight - 1;
            }
        }

        /// <summary>
        /// 主界面“查找标题”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定级别的标题。
        /// 这两个按钮看起来就是个 SplitButton。此方法用以实现类似 SplitButton 的效果。
        /// </summary>
        private void btnFindHeaders_AppendArrow_Initialized(object sender, EventArgs e)
        {
            //SplitterButton的弹出菜单要处理一下，左键单显示，右键不显示
            splitMenu2.PlacementTarget = btnFindHeaders_AppendArrow;
            splitMenu2.Placement = PlacementMode.Bottom;

            btnFindHeaders_AppendArrow.ContextMenu = null;

            splitMenu2.Opened += SplitMenu2_Opened;
        }

        private void SplitMenu2_Opened(object sender, RoutedEventArgs e)
        {
            //var point = btnFindHeaders_AppendArrow.PointToScreen(new Point(0, 0));
            if (Environment.OSVersion.Version.Major > 6 ||
                (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))  // win 8 以上（6.2）
            {
                splitMenu2.HorizontalOffset = -splitMenu2.ActualWidth + btnFindHeaders_AppendArrow.ActualWidth + 6;// point.X - splitMenu2.ActualWidth + btnFindHeaders_AppendArrow.ActualWidth;
                splitMenu2.VerticalOffset = 2;// point.Y - 8;
            }
            else
            {
                splitMenu2.HorizontalOffset = -splitMenu2.ActualWidth + btnFindHeaders_AppendArrow.ActualWidth;
                splitMenu2.VerticalOffset = 2;// point.Y + btnFindHeaders_AppendArrow.ActualHeight - 1;
            }
        }

        /// <summary>
        /// 主界面“查找任务列表”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定类型的任务列表。
        /// 这两个按钮看起来就是个 SplitButton。
        /// </summary>
        private void btnFindTaskListItems_AppendArrow_Click(object sender, RoutedEventArgs e)
        {
            splitMenu.IsOpen = true;
        }

        /// <summary>
        /// 主界面“查找标题”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定级别的标题。
        /// 这两个按钮看起来就是个 SplitButton。
        /// </summary>
        private void btnFindHeaders_AppendArrow_Click(object sender, RoutedEventArgs e)
        {
            splitMenu2.IsOpen = true;
        }

        /// <summary>
        /// 用“region { } region”块包围当前选定的文本行。
        /// </summary>
        private void miWrapWithRegionMark_Click(object sender, RoutedEventArgs e)
        {
            WrapWithRegionMark();
        }

        internal void WrapWithRegionMark()
        {
            var editor = ActivedEditor;

            if (editor.EditorBase.SelectionLength == 0)
            {
                editor.EditorBase.SelectedText = "\r\n{ \r\n\r\n\r\n}\r\n";
                var destSel = editor.EditorBase.SelectionStart + 4;
                if (destSel < editor.EditorBase.Document.TextLength)
                {
                    editor.EditorBase.Select(destSel, 0);
                }
                return;
            }

            var fstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart);
            var lastLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart + editor.EditorBase.SelectionLength);

            editor.EditorBase.Document.Insert(lastLine.EndOffset, "\r\n}\r\n");
            editor.EditorBase.Document.Insert(fstLine.Offset, "\r\n{ \r\n");
            var destSel2 = fstLine.Offset + 4;
            if (destSel2 < editor.EditorBase.Document.TextLength)
            {
                editor.EditorBase.Select(destSel2, 0);
            }
        }

        /// <summary>
        /// 打印预览当前活动编辑器。
        /// </summary>
        private void miPrintPreview_Click(object sender, RoutedEventArgs e)
        {
            PrintPreview();
        }

        /// <summary>
        /// 打印当前活动编辑器。
        /// </summary>
        private void miPrintDocument_Click(object sender, RoutedEventArgs e)
        {
            PrintDocument();
        }

        /// <summary>
        /// 对当前活动编辑器执行“打印预览”。
        /// </summary>
        public void PrintPreview()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            if (editor.EditorBase.PageSetupDialog())              // .NET dialog
            {
                editor.EditorBase.PrintPreviewDialog(editor.ShortFileName);           // WPF print preview dialog
            }
        }

        /// <summary>
        /// 打印当前活动编辑器。
        /// </summary>
        public void PrintDocument()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            if (editor.EditorBase.PageSetupDialog())              // .NET dialog
            {
                editor.EditorBase.PrintDialog(editor.ShortFileName);           // WPF print preview dialog
            }

            //调用方法示例，勿删。
            //editor.EditorBase.PrintPreviewDialog(filename);   // WPF print preview dialog, filename as document title
            //editor.EditorBase.PrintDialog();                   // WPF print dialog
            //editor.EditorBase.PrintDialog(filename);          // WPF print dialog, filename as document title
            //editor.EditorBase.PrintDirect();                   // prints to default or previously selected printer
            //editor.EditorBase.PrintDirect(filename)           // prints to default or previously selected printer, filename as document title
        }

        /// <summary>
        /// 查找功能在启用“正则表达式”时，禁止使用“通配符”。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbRegex_Checked(object sender, RoutedEventArgs e)
        {
            cbWildcards.IsChecked = false;
        }

        /// <summary>
        /// 查找功能在启用“通配符”时，禁止使用“正则表达式”。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbWildcards_Checked(object sender, RoutedEventArgs e)
        {
            cbRegex.IsChecked = false;
        }

        /// <summary>
        /// 开关“AutoNumberHeaders”。开启后编译的 Html 文档中所有“标题<h1></h1>...<h6></h6>”都会自动添加序号。
        /// </summary>
        private void miAutoNumberHeaders_Click(object sender, RoutedEventArgs e)
        {
            miAutoNumberHeaders.IsChecked = !miAutoNumberHeaders.IsChecked;
            this.AutoNumberHeaders = miAutoNumberHeaders.IsChecked;
            App.WorkspaceConfigManager.Set("AutoNumberHeaders", miAutoNumberHeaders.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "AutoNumberHeaders",
                HtmlOptionText = "自动编号",
            });
        }

        /// <summary>
        /// 是否采用中文公文样式的自动编号。
        /// </summary>
        private void miAutoNumberStyle_Click(object sender, RoutedEventArgs e)
        {
            var lang = (sender as MenuItem).Tag.ToString();
            if (string.IsNullOrWhiteSpace(lang)) lang = "en_us";

            this.autoNumberStyle = lang;

            switch (lang)
            {
                case "zh_cn":
                    {
                        miAutoNumberChineseStyle.IsChecked = true;
                        miAutoNumberEnglishStyle.IsChecked = false;
                        App.WorkspaceConfigManager.Set("AutoNumberStyle", "zh_cn");

                        OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
                        {
                            HtmlOptionID = "AutoNumberStyle",
                            HtmlOptionText = "自动编号采用中文样式",
                        });
                        break;
                    }
                default:
                    {
                        miAutoNumberChineseStyle.IsChecked = false;
                        miAutoNumberEnglishStyle.IsChecked = true;
                        App.WorkspaceConfigManager.Set("AutoNumberStyle", "en_us");

                        OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
                        {
                            HtmlOptionID = "AutoNumberStyle",
                            HtmlOptionText = "自动编号采用英文样式",
                        });
                        break;
                    }
            }

        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有六个级别的标题。
        /// </summary>
        private void btnFindHeaders_Click(object sender, RoutedEventArgs e)
        {
            string keyWord = @"^ {0,3}#{1,6}.*$";
            FindHeaders(keyWord, tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有六个级别的标题。须指定用于查找标题的正则表达式。
        /// </summary>
        /// <param name="keyWord">要用于查找标题的正则表达式。</param>
        /// <param name="treeView">指定要将查找结果输出到哪个树型框。</param>
        private void FindHeaders(string keyWord, TreeView treeView)
        {
            FindText(keyWord, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), true, treeView);

            if (treeView.HasItems == false)
            {
                treeView.Items.Add(new TextBlock() { Text = "<...未找到结果...>", });
            }

            //防止右工具栏被折叠
            if (treeView == tvFindAndReplace)
            {
                if (tcRightToolBar.SelectedItem != tiFindResult)
                {
                    tcRightToolBar.SelectedItem = tiFindResult;
                }

                if (cdRightToolsArea.ActualWidth < 140)
                {
                    cdRightToolsArea.Width = new GridLength(2, GridUnitType.Star);
                }
            }
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#”开头的标题。
        /// </summary>
        private void miSearchH1_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“##”开头的标题。
        /// </summary>
        private void miSearchH2_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}##[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“###”开头的标题。
        /// </summary>
        private void miSearchH3_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}###[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“####”开头的标题。
        /// </summary>
        private void miSearchH4_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}####[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#####”开头的标题。
        /// </summary>
        private void miSearchH5_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#####[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“######”开头的标题。
        /// </summary>
        private void miSearchH6_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}######.*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#”开头的标题。
        /// </summary>
        private void miSearchAllH_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#{1,6}.*$", tvFindAndReplace);
        }

        /// <summary>
        /// 打开 LME 教程（这是个 chm 文档）。
        /// </summary>
        private void btnBook_Click(object sender, RoutedEventArgs e)
        {
            OpenTutorial();
        }

        private void OpenTutorial()
        {
            var bookFullName = Globals.InstalledPath + "Lunar_Markdown_Editor_教程.chm";
            if (File.Exists(bookFullName))
            {
                System.Diagnostics.Process.Start(bookFullName);
            }
        }

        private void btnIronPythonForLmeBook_Click(object sender, RoutedEventArgs e)
        {
            OpenIronPythonForLmeTutorial();
        }

        private void OpenIronPythonForLmeTutorial()
        {
            var bookFullName = Globals.InstalledPath + "ironpython-for-lme.chm";
            if (File.Exists(bookFullName))
            {
                System.Diagnostics.Process.Start(bookFullName);
            }
        }
        private List<EnToChEntry> dictionary = new List<EnToChEntry>();
        /// <summary>
        /// 英译中词典，用于智能提示功能。
        /// </summary>
        public List<EnToChEntry> Dictionary
        {
            get { return dictionary; }
        }

        /// <summary>
        /// 向英译中词典中添加词条。
        /// </summary>
        private void miAddEnToChEntry_Click(object sender, RoutedEventArgs e)
        {
            var window = new AddEnToChEntryWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
            };
            window.EntryAdded += Window_EntryAdded;
            window.Show();
        }

        /// <summary>
        /// 自动完成提示窗口中添加词条时触发此方法。
        /// </summary>
        private void Window_EntryAdded(object sender, EntryAddedEventArgs e)
        {
            dictionary.Add(e.Entry);
            foreach (var i in this.mainTabControl.Items)
            {
                var edit = i as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.AddEntryToCompletionWindow(e.Entry);
            }
        }

        /// <summary>
        /// 编辑英译中词典中的“用户词典”。
        /// </summary>
        private void miEditEnToChUserDictonary_Click(object sender, RoutedEventArgs e)
        {
            var result = EditDictionary(Globals.PathOfUserDictionary, Globals.AppName + " - 编辑用户词典");
            if (result != string.Empty)
            {
                LMessageBox.Show("不能编辑用户词典。错误消息如下：\r\n" + result,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                LoadDictionary();
            }
        }

        /// <summary>
        /// 编辑英译中词典中的“工作区词典”。
        /// </summary>
        private void miEditEnToChWorkspaceDictionary_Click(object sender, RoutedEventArgs e)
        {
            var result = EditDictionary(Globals.PathOfWorkspaceDictionary, Globals.AppName + " - 编辑工作区词典");
            if (result != string.Empty)
            {
                LMessageBox.Show("不能编辑工作区词典。错误消息如下：\r\n" + result,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                LoadDictionary();
            }
        }

        /// <summary>
        /// 编辑英译中词典文件。
        /// </summary>
        /// <param name="filePath">要编辑的词典文件的完整路径。</param>
        /// <param name="editorWindowTitle">编辑器窗口标题文本。</param>
        /// <returns>返回 string.Empty 表示一切正常完成。</returns>
        private string EditDictionary(string filePath, string editorWindowTitle = null)
        {
            try
            {
                if (File.Exists(filePath) == false)
                {
                    File.WriteAllText(filePath, "; 英文\t中文\t字母缩写\t注释\r\n", Encoding.UTF8);
                }

                if (File.Exists(filePath) == false)
                {
                    return "未找到文件且无法创建。";
                }

                //编辑词典文件
                var dictionaryEditor = new DictionaryEditor(filePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                };

                if (string.IsNullOrWhiteSpace(editorWindowTitle))
                {
                    dictionaryEditor.Title = Globals.AppName;
                }
                else { dictionaryEditor.Title = editorWindowTitle; }

                dictionaryEditor.ShowDialog();

                #region 旧版，以纯文本形式编辑，不够直观。
                //var pw = new PlainTextEditor(filePath)
                //{
                //    Owner = this,
                //    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                //    Title = Globals.AppName + " - 编辑词典",
                //};

                //using (StreamReader sr = new StreamReader(filePath))
                //{
                //    var content = sr.ReadToEnd();
                //    string fstLine = "";
                //    int index = content.IndexOf("\r\n");

                //    if (index >= 0)
                //    {
                //        fstLine = content.Substring(0, index - 2);
                //    }

                //    if (fstLine.StartsWith(";格式说明：") == false)
                //    {
                //        content = ";格式说明： 英文[Tab]中文[Tab]字母缩写[Tab]注释\r\n" + content;
                //    }

                //    pw.tbx.Text = content;
                //}

                //pw.ShowDialog(); 
                #endregion
                return string.Empty;
            }
            catch (Exception ex)
            {
                return ex.Message + "\r\n" + ex.StackTrace;
            }
        }

        /// <summary>
        /// 开关“自动提示译词”功能。
        /// </summary>
        private void miIsEnToChineseDictEnabled_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsAutoCompletionEnabled == false)
            {
                var result = LMessageBox.Show("需要打开【自动完成】，要继续吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;

                miIsAutoCompletionEnabled.IsChecked = true;
                this.IsAutoCompletionEnabled = true;
                App.ConfigManager.Set("IsAutoCompletionEnabled", true.ToString());
            }

            miIsEnToChineseDictEnabled.IsChecked = !miIsEnToChineseDictEnabled.IsChecked;
            this.IsEnToChineseDictEnabled = miIsEnToChineseDictEnabled.IsChecked;
            App.ConfigManager.Set("IsEnToChineseDictEnabled", miIsEnToChineseDictEnabled.IsChecked.ToString());
        }

        /// <summary>
        /// 整理活动编辑器中的空段落，将过多的空段合并。
        /// </summary>
        private void miFormatParagraphs_Click(object sender, RoutedEventArgs e)
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            cbRegex.IsChecked = true;
            cmbFindText.Text = @"(\r\n){2,}";
            cmbReplaceTextInputBox.Text = @"\r\n";
            cmbSearchArea.SelectedIndex = 0;

            if (edit.EditorBase.SelectionLength == 0)
            {
                //当前未选中文本，在整个文档中替换。
                btnReplaceAll_Click(sender, e);
            }
            else
            {
                //当前选中了文本，只在选定的各行中替换
                bool shouldRestore = false;
                if (cbReplaceOnlyInSelectionText.IsChecked != true)
                {
                    cbReplaceOnlyInSelectionText.IsChecked = true;
                    shouldRestore = true;
                }

                btnReplaceAll_Click(sender, e);

                if (shouldRestore)
                {
                    cbReplaceOnlyInSelectionText.IsChecked = false;
                }
            }
        }

        /// <summary>
        /// 折叠左工具栏。
        /// </summary>
        private void btnCollapseLeftToolArea_Click(object sender, RoutedEventArgs e)
        {
            SwitchLeftToolBarToggle();
        }

        /// <summary>
        /// 折叠右工具栏。
        /// </summary>
        private void btnCollapseRightToolArea_Click(object sender, RoutedEventArgs e)
        {
            SwitchRightToolBarToggle();
        }

        /// <summary>
        /// 折叠“查找/替换”面板。
        /// </summary>
        private void btnCollapseFindAndReplacePanel_Click(object sender, RoutedEventArgs e)
        {
            SwitchFindAndReplacePanelToggle();
        }

        /// <summary>
        /// 切换 AppendTimeOfCompiling 属性的值。该属性用于决定是否在编译的 Html 文档末尾添加“编译时间文本”。
        /// </summary>
        private void miAppendTimeOfCompiling_Click(object sender, RoutedEventArgs e)
        {
            this.AppendTimeOfCompiling =
                        miAppendTimeOfCompiling.IsChecked = !miAppendTimeOfCompiling.IsChecked;
            App.WorkspaceConfigManager.Set("AppendTimeOfCompiling", miAppendTimeOfCompiling.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "AppendTimeOfCompiling",
                HtmlOptionText = "在 Html 末尾附加编译时间",
            });
        }

        /// <summary>
        /// 是否在编译Html时将Markdown文件中单独占一行的图像文件链接的标题编译到图像顶部。默认情况下是编译到底部的。
        /// </summary>
        private void miTableAndImageCaptionAtTop_Click(object sender, RoutedEventArgs e)
        {
            this.ImageTitleAtTop =
                        miImageTitleAtTop.IsChecked = !miImageTitleAtTop.IsChecked;
            App.WorkspaceConfigManager.Set("ImageTitleAtTop", miImageTitleAtTop.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "ImageTitleAtTop",
                HtmlOptionText = "单行图像标题在顶部",
            });
        }

        /// <summary>
        /// 是否在编译Html时将二维文字表的标题编译到底部。默认情况下是编译到顶部的。
        /// </summary>
        private void miTableCaptionAtBottom_Click(object sender, RoutedEventArgs e)
        {
            this.TableCaptionAtBottom =
                        miTableCaptionAtBottom.IsChecked = !miTableCaptionAtBottom.IsChecked;
            App.WorkspaceConfigManager.Set("TableCaptionAtBottom", miTableCaptionAtBottom.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "TableCaptionAtBottom",
                HtmlOptionText = "二维文字表标题在底部",
            });
        }

        /// <summary>
        /// 切换 FormatAfterCompile 属性的值。该属性用于决定是否对编译好的 Html 文件进行格式化。
        /// **对 Html 的格式化是用 NSoup 实现的。
        /// </summary>
        private void miFormatAfterCompile_Click(object sender, RoutedEventArgs e)
        {
            this.FormatAfterCompile =
            miFormatAfterCompile.IsChecked = !miFormatAfterCompile.IsChecked;
            App.WorkspaceConfigManager.Set("FormatAfterCompile", miFormatAfterCompile.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "FormatAfterCompile",
                HtmlOptionText = "格式化编译的 Html 文档",
            });
        }

        /// <summary>
        /// 查找所有 TODO Comment，查找结果显示在右工具栏“查找结果”页面。
        /// </summary>
        private void miSearchAllTodoComment_Click(object sender, RoutedEventArgs e)
        {
            string mark = (sender as MenuItem).Tag.ToString();
            tvTaskList.Items.Clear();
            FindTodoComment((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), tvTaskList, mark);

            if (tvTaskList.Items.Count <= 0)
            {
                tvTaskList.Items.Add(new TreeViewItem() { Header = $"<没找到 {mark} 注释...>", });
            }

            //防止右工具栏被隐藏。
            if (tcRightToolBar.SelectedItem != tiTaskList)
            {
                tcRightToolBar.SelectedItem = tiTaskList;
            }

            if (cdRightToolsArea.ActualWidth < 100)
            {
                cdMainEditArea.Width =
                    cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 根据指定的查找范围来查找所有 TODO Comment，含（TODO/DONE等）。
        /// </summary>
        /// <param name="searchArea">查找范围：包括“ActiveDocument”、“OpenedDocuments”、“AllFiles”。</param>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        /// <param name="mark">传入“TODO”（或“DONE”、“DOING”等）标记文本。</param>
        private void FindTodoComment(string searchArea, TreeView treeView, string mark)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            switch (searchArea)
            {
                case "ActiveDocument":
                    {
                        FindTodoCommentsInActiveDocuments(treeView, mark);
                        break;
                    }
                case "OpenedDocuments":
                    {
                        FindTodoCommentsInOpenedDocuments(treeView, mark);
                        break;
                    }
                case "AllFiles":
                    {
                        //这个需要用到递归
                        FindTodoCommentInAllFiles(Globals.PathOfWorkspace, treeView, mark);
                        break;
                    }
                case "CheckedFiles":
                    {
                        FindTodoCommentInAllFiles(Globals.PathOfWorkspace, treeView, mark, true);
                        break;
                    }
            }
        }

        /// <summary>
        /// 在所有打开的文档中查找 TODO Comments。
        /// </summary>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        /// <param name="mark">传入TODO标记。（“TODO”或“DONE”等）。</param>
        private void FindTodoCommentsInOpenedDocuments(TreeView treeView, string mark = "TODO")
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    Brush markBrush;
                    switch (mark)
                    {
                        case "DONE": { markBrush = Brushes.Blue; break; }
                        case "DOING": { markBrush = Brushes.Green; break; }
                        default: { markBrush = Brushes.Red; break; }
                    }

                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    var lines = efi.EditorBase.Document.Lines;
                    for (int i = 0; i < lines.Count; i++)
                    {
                        var line = lines[i];
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        if (string.IsNullOrEmpty(lineText)) continue;

                        string todoCommentMark;
                        string todoCommentTail;
                        if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail, mark))
                        {
                            FindLineTreeViewItem newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                                todoCommentMark.Length, lineText.Length - todoCommentMark.Length, lineText, markBrush, FindLineTreeViewItem.ItemType.TodoComment);
                            fdi.Items.Add(newItem);
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在活动文档中查找所有 TODO Comments。
        /// </summary>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        /// <param name="mark">传入TODO标记。（“TODO”或“DONE”等）。</param>
        private void FindTodoCommentsInActiveDocuments(TreeView treeView, string mark = "TODO")
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                Brush markBrush;
                switch (mark)
                {
                    case "DONE": { markBrush = Brushes.Blue; break; }
                    case "DOING": { markBrush = Brushes.Green; break; }
                    default: { markBrush = Brushes.Red; break; }
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                var lines = efi.EditorBase.Document.Lines;
                for (int i = 0; i < lines.Count; i++)
                {
                    var line = lines[i];
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrEmpty(lineText)) continue;

                    string todoCommentMark;
                    string todoCommentTail;
                    if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail, mark))
                    {
                        FindLineTreeViewItem newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                            todoCommentMark.Length, lineText.Length - todoCommentMark.Length, lineText, markBrush, FindLineTreeViewItem.ItemType.TodoComment);
                        fdi.Items.Add(newItem);
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// 从指定目录开始查找能找到的所有 Markdown 文件（含子目录中的文件）中的 TODO Comment。
        /// </summary>
        /// <param name="directoryPath">开始查找的目录全路径。</param>
        /// <param name="treeView">指定将查找结果输出到哪个树型框。</param>
        /// <param name="mark">传入TODO标记。（“TODO”或“DONE”等）。</param>
        private void FindTodoCommentInAllFiles(string directoryPath, TreeView treeView, string mark = "TODO", bool onlyCheckedFiles = false)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directory = new DirectoryInfo(directoryPath);

            //先处理当前目录下的文件
            var childFilesInfos = directory.GetFiles();

            foreach (var childFileInfo in childFilesInfos)
            {
                if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                if (onlyCheckedFiles)
                {
                    var wtvi = FindWorkspaceTreeViewItem(childFileInfo.FullName);
                    if (wtvi == null || wtvi.IsChecked != true) continue;
                }

                string[] lines;

                //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                if (fileEditor != null)
                {
                    lines = fileEditor.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' });
                }
                else
                {
                    lines = File.ReadAllLines(childFileInfo.FullName);
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                var lineNum = 0;
                for (int i = 0; i < lines.Length; i++)
                {
                    lineNum++;

                    var lineText = lines[i];
                    if (string.IsNullOrEmpty(lineText)) continue;

                    string todoCommentMark;
                    string todoCommentTail;
                    if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail, mark))
                    {
                        Brush markBrush;
                        switch (mark)
                        {
                            case "DONE": { markBrush = Brushes.Blue; break; }
                            case "DOING": { markBrush = Brushes.Green; break; }
                            default: { markBrush = Brushes.Red; break; }
                        }

                        FindLineTreeViewItem newItem = new FindLineTreeViewItem(childFileInfo.FullName,
                            childFileInfo.Name, lineNum, todoCommentMark.Length, lineText.Length - todoCommentMark.Length,
                            lineText, markBrush, FindLineTreeViewItem.ItemType.TodoComment);
                        fdi.Items.Add(newItem);
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }

            //再递归处理子目录下的文件
            var childDirectories = directory.GetDirectories();
            foreach (var childDirectory in childDirectories)
            {
                FindTodoCommentInAllFiles(childDirectory.FullName, treeView, mark);
            }
        }

        /// <summary>
        /// 折叠左工具栏底部的资源预览区域。
        /// </summary>
        private void btnResetLeftToolbarLayout_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (PerspectiveMode == Perspective.EditingAndPresentation)
            {
                //把演讲模式下的行为和其它透视图下的行为区分开来。
                rdLeftToolsTop.Height =
                    rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
            }
            else
            {
                //在没有想到更好的办法之前，还是简单点儿（折叠/展开）好。
                if (rdResourcePreviewArea.ActualHeight <= 0)
                {
                    rdLeftToolsTop.Height =
                        rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
                }
                else
                {
                    rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                    rdResourcePreviewArea.Height = new GridLength(0);
                }

                //三态切换，行为有些诡异，还是先废除的好
                //在不同的状态间循环切换。
                //if (rdResourcePreviewArea.ActualHeight <= 0)
                //{
                //    rdLeftToolsTop.Height =
                //        rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
                //}
                //else if (rdResourcePreviewArea.Height.GridUnitType != GridUnitType.Star)//拖动调整过
                //{
                //    rdLeftToolsTop.Height = new GridLength(0);
                //    rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
                //}
                //else
                //{
                //    rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                //    rdResourcePreviewArea.Height = new GridLength(0);
                //}
            }
        }

        /// <summary>
        /// 手工指定（设置）用户自行下载、安装的微软公司 Html Help Workshop 应用程序的磁盘位置。
        /// </summary>
        private void miSetHtmlHelpWorkshopFullName_Click(object sender, RoutedEventArgs e)
        {
            SetHtmlHelpWorkshopFullName();
        }

        /// <summary>
        /// 在工作区中当前选定的目录的父目录中创建 Markdown 文件。
        /// 目录较长时，再回去找父目录会比较烦人。
        /// </summary>
        private void miNewSameLevelFile_Click(object sender, RoutedEventArgs e)
        {
            NewSameLevelFile();
        }

        internal void NewSameLevelFile()
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请先在工作区选择一个条目作为创建文件的位置。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsResourceDirectory || selItem.IsResourceFile)
            {
                LMessageBox.Show("资源文件夹或资源文件附近不能创建 Markdown 文件。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsDirectoryExists)
            {
                var parentItem = selItem.Parent as WorkspaceTreeViewItem;
                if (parentItem == null)
                {
                    LMessageBox.Show("找不到当前位置的上级，无法确定同级文档应该创建在什么位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                parentItem.IsSelected = true;
                var index = parentItem.Items.IndexOf(selItem);
                NewFile(false, false, parentItem, index + 1);
            }
            else if (selItem.IsMarkdownFilePath)
            {
                NewFile(false);
            }
        }

        /// <summary>
        /// 调用 Windows 系统中安装的 Lunar Concept 绘制概念图。
        /// </summary>
        private void miInsertConcept_Click(object sender, RoutedEventArgs e)
        {
            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Concept";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();

            if (File.Exists(lunarConceptInstalledPath) == false)
            {
                LMessageBox.Show("此计算机上未安装 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string imageResourceFolderPath;
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem imageResourceItem = null;

            if (selItem.IsMarkdownFilePath)
            {
                var filePath = selItem.FullPath;
                imageResourceFolderPath = selItem.FullPath.Substring(0, selItem.FullPath.Length - 3) + "~\\Images~\\";
            }
            else if (selItem.IsImageResourceDirectory)
            {
                imageResourceFolderPath = selItem.FullPath;
                imageResourceItem = selItem;
            }
            else if (selItem.IsDirectoryExists)
            {
                var directoryInfo = new DirectoryInfo(selItem.FullPath);
                var metaFileImageResourceDirectoryPath =
                    (directoryInfo.FullName.EndsWith("\\") ? directoryInfo.FullName : (directoryInfo.FullName + "\\")) +
                    $"_{directoryInfo.Name}~\\Images~\\";
                imageResourceFolderPath = metaFileImageResourceDirectoryPath;
                foreach (var item in selItem.Items)
                {
                    var wtvisub = item as WorkspaceTreeViewItem;
                    if (wtvisub == null) continue;
                    if (wtvisub.FullPath == metaFileImageResourceDirectoryPath)
                    {
                        imageResourceItem = wtvisub;
                        break;
                    }
                }
            }
            else
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                var shortCommentName = InputBox.Show(Globals.AppName, "　　请输入概念图短名（不能以“_”开头，尽量以字母开头）：", "概念图", true, "不需要后缀名。", false);

                var shortName = ChinesePinYin.ToChinesePinYinText(shortCommentName);

                if (AskEnglishFileName)
                {
                    var regValidFileNameChars = new Regex(@"^[a-zA-Z0-9_][a-zA-Z0-9\-~_]{0,}");
                    if (regValidFileNameChars.IsMatch(shortName) == false)
                    {
                        // 为什么要用英文文件名？
                        // 这是因为 CHM 对中文路径的支持有问题——很多网络设备对中文路径的支持也有问题。
                        shortName = InputBox.Show(Globals.AppName, $"您输入的【{shortCommentName}】将被用作概念图的标题。\r\n\r\n由于含有中文或特殊字符，建议使用下列字符串作目录名：",
                            shortName, true, "说明：\r\n　　⑴建议的文件名是取中文拼音首字母生成的。\r\n　　⑵您可以在这里改成有意义的英文字符串。\r\n　　⑶空白字符会被替换为连字符。", false, true);
                        if (string.IsNullOrWhiteSpace(shortName)) return;  // 用户放弃。
                    }
                }

                shortName = ChinesePinYin.ToChinesePinYinText(shortName.Replace(" ", "-").Replace("　", "-").Replace("\t", "-"));  // 防止用户仍然不慎输入了中文。

                if (string.IsNullOrWhiteSpace(shortName) || shortName.StartsWith(".")) return;

                if (shortName.ToLower().EndsWith(".png") == false)
                {
                    shortName += ".png";
                }

                //刷新工作区管理器
                if (Directory.Exists(imageResourceFolderPath) == false)
                {
                    Directory.CreateDirectory(imageResourceFolderPath);

                    //须解决效率问题，不能直接调用此方法，也不能直接载入Xml。
                    //WorkspaceManager.RefreshWorkspaceTreeView(selItem.FullPath);

                    //基本思路：先添加“Images~”资源文件夹节点，再添加资源节点。
                    imageResourceItem = new WorkspaceTreeViewItem(
                        (Globals.MainWindow.ShowTitleInWorkspaceManager ? "图像~" : "Images~"),
                        imageResourceFolderPath, "[-]", "", "图像资源文件夹", false,
                        WorkspaceTreeViewItem.Type.ImageFolder, 0, Globals.MainWindow);
                    selItem.Items.Add(imageResourceItem);
                }
                else
                {
                    imageResourceItem = FindWorkspaceTreeViewItem(imageResourceFolderPath);
                }

                var destFileName = imageResourceFolderPath + shortName;

                if (File.Exists(destFileName))
                {
                    LMessageBox.Show("已存在同名文件，操作无法完成。", Globals.AppName,
                               MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                File.Copy(Globals.InstalledPath + "ConceptExample.png", destFileName);

                var newImageItem = new WorkspaceTreeViewItem(destFileName, Globals.MainWindow);

                if (imageResourceItem != null)
                {
                    imageResourceItem.Items.Add(newImageItem);
                    newImageItem.IsSelected = true;
                }

                var destFileInfo = new FileInfo(destFileName);
                var oldExtension = destFileInfo.Extension;

                var commentTextFilePath = destFileInfo.Directory.FullName + "\\" + "~.txt";

                if (shortCommentName.ToLower().EndsWith(".png") == false)
                {
                    shortCommentName += ".png";
                }

                if (File.Exists(commentTextFilePath))
                {
                    using (var stream = File.AppendText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }
                else
                {
                    using (var stream = File.CreateText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }

                newImageItem.RefreshFileState();     //取注释文件中的标题文本
                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{destFileName}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 编辑由 Lunar Concept 生成的 png 格式的概念图。
        /// Lunar Concept 生成的 png 图形末尾被附加了一个纯文本的 XML 文件的所有内容，所以是可以编辑的。
        /// </summary>
        private void miEditConcept_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null || selItem.IsImageFileExist == false)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“png”资源文件（该资源文件必须是由 Lunar Concept 生成的才行）。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Concept";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            else
            {
                string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();
                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{selItem.FullPath}\"");
            }
        }

        /// <summary>
        /// 调用 Windows 系统中安装的 Lunar Mind 绘制概念图。
        /// </summary>
        private void miInsertMindMap_Click(object sender, RoutedEventArgs e)
        {
            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Mind";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();

            if (File.Exists(lunarConceptInstalledPath) == false)
            {
                LMessageBox.Show("此计算机上未安装 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string imageResourceFolderPath;
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem imageResourceItem = null;

            if (selItem.IsMarkdownFilePath)
            {
                var filePath = selItem.FullPath;
                imageResourceFolderPath = selItem.FullPath.Substring(0, selItem.FullPath.Length - 3) + "~\\Images~\\";
            }
            else if (selItem.IsImageResourceDirectory)
            {
                imageResourceFolderPath = selItem.FullPath;
                imageResourceItem = selItem;
            }
            else if (selItem.IsDirectoryExists)
            {
                var directoryInfo = new DirectoryInfo(selItem.FullPath);
                var metaFileImageResourceDirectoryPath =
                    (directoryInfo.FullName.EndsWith("\\") ? directoryInfo.FullName : (directoryInfo.FullName + "\\")) +
                    $"_{directoryInfo.Name}~\\Images~\\";
                imageResourceFolderPath = metaFileImageResourceDirectoryPath;
                foreach (var item in selItem.Items)
                {
                    var wtvisub = item as WorkspaceTreeViewItem;
                    if (wtvisub == null) continue;
                    if (wtvisub.FullPath == metaFileImageResourceDirectoryPath)
                    {
                        imageResourceItem = wtvisub;
                        break;
                    }
                }
            }
            else
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加思维导图文件的位置。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                var shortCommentName = InputBox.Show(Globals.AppName, "　　请输入思维导图短名（不能以“_”开头，尽量以字母开头）：", "思维导图", true, "不需要后缀名。", false);

                var shortName = ChinesePinYin.ToChinesePinYinText(shortCommentName);

                if (string.IsNullOrWhiteSpace(shortName) || shortName.StartsWith(".")) return;

                if (AskEnglishFileName)
                {
                    var regValidFileNameChars = new Regex(@"^[a-zA-Z0-9_][a-zA-Z0-9\-~_]{0,}");
                    if (regValidFileNameChars.IsMatch(shortName) == false)
                    {
                        // 为什么要用英文文件名？
                        // 这是因为 CHM 对中文路径的支持有问题——很多网络设备对中文路径的支持也有问题。
                        shortName = InputBox.Show(Globals.AppName, $"您输入的【{shortCommentName}】将被用作思维导图的标题。\r\n\r\n由于含有中文或特殊字符，建议使用下列字符串作目录名：",
                            shortName, true, "说明：\r\n　　⑴建议的文件名是取中文拼音首字母生成的。\r\n　　⑵您可以在这里改成有意义的英文字符串。\r\n　　⑶空白字符会被替换为连字符。", false, true);
                        if (string.IsNullOrWhiteSpace(shortName)) return;  // 用户放弃。
                    }
                }

                shortName = ChinesePinYin.ToChinesePinYinText(shortName.Replace(" ", "-").Replace("　", "-").Replace("\t", "-"));  // 防止用户仍然不慎输入了中文。

                if (shortName.ToLower().EndsWith(".png") == false)
                {
                    shortName += ".png";
                }

                //刷新工作区管理器
                if (Directory.Exists(imageResourceFolderPath) == false)
                {
                    Directory.CreateDirectory(imageResourceFolderPath);

                    //须解决效率问题，不能直接调用此方法，也不能直接载入Xml。
                    //WorkspaceManager.RefreshWorkspaceTreeView(selItem.FullPath);

                    //基本思路：先添加“Images~”资源文件夹节点，再添加资源节点。
                    imageResourceItem = new WorkspaceTreeViewItem(
                        (Globals.MainWindow.ShowTitleInWorkspaceManager ? "图像~" : "Images~"),
                        imageResourceFolderPath, "[-]", "", "图像资源文件夹", false,
                        WorkspaceTreeViewItem.Type.ImageFolder, 0, Globals.MainWindow);
                    selItem.Items.Add(imageResourceItem);
                }
                else
                {
                    imageResourceItem = FindWorkspaceTreeViewItem(imageResourceFolderPath);
                }

                var destFileName = imageResourceFolderPath + shortName;
                if (File.Exists(destFileName))
                {
                    LMessageBox.Show("已存在同名文件，操作无法完成。", Globals.AppName,
                               MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                File.Copy(Globals.InstalledPath + "MindMapExample.png", destFileName);

                var newImageItem = new WorkspaceTreeViewItem(destFileName, Globals.MainWindow);

                if (imageResourceItem != null)
                {
                    imageResourceItem.Items.Add(newImageItem);
                    newImageItem.IsSelected = true;
                }

                var destFileInfo = new FileInfo(destFileName);
                var oldExtension = destFileInfo.Extension;

                var commentTextFilePath = destFileInfo.Directory.FullName + "\\" + "~.txt";

                if (shortCommentName.ToLower().EndsWith(".png") == false)
                {
                    shortCommentName += ".png";
                }

                if (File.Exists(commentTextFilePath))
                {
                    using (var stream = File.AppendText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }
                else
                {
                    using (var stream = File.CreateText(commentTextFilePath))
                    {
                        stream.WriteLine($"{shortName}|{shortCommentName}");
                    }
                }

                newImageItem.RefreshFileState();  //取注释文件中的标题文本
                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{destFileName}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 编辑由 Lunar Concept 生成的 png 格式的概念图。
        /// Lunar Concept 生成的 png 图形末尾被附加了一个纯文本的 XML 文件的所有内容，所以是可以编辑的。
        /// </summary>
        private void miEditMindMap_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null || selItem.IsImageFileExist == false)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“png”资源文件（该资源文件必须是由 Lunar Map 生成的才行）。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //尝试从注册表中读取安装的LunarMind.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Mind";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            else
            {
                string lunarMindInstalledPath = regSubKey.GetValue("InstalledPath").ToString();
                System.Diagnostics.Process.Start(lunarMindInstalledPath, $"\"{selItem.FullPath}\"");
            }
        }

        private void miShowSpaces_Click(object sender, RoutedEventArgs e)
        {
            miShowSpaces.IsChecked = !miShowSpaces.IsChecked;
            this.IsShowSpaces = miShowSpaces.IsChecked;
            App.ConfigManager.Set("IsShowSpaces", this.IsShowSpaces.ToString());
        }

        private void miShowEndOfLine_Click(object sender, RoutedEventArgs e)
        {
            miShowEndOfLine.IsChecked = !miShowEndOfLine.IsChecked;
            this.IsShowEndOfLine = miShowEndOfLine.IsChecked;
            App.ConfigManager.Set("IsShowEndOfLine", this.isShowEndOfLine.ToString());
        }

        private void miShowTabs_Click(object sender, RoutedEventArgs e)
        {
            miShowTabs.IsChecked = !miShowTabs.IsChecked;
            this.IsShowTabs = miShowTabs.IsChecked;
            App.ConfigManager.Set("IsShowTabs", this.IsShowTabs.ToString());
        }

        private void miShowLineNumbers_Click(object sender, RoutedEventArgs e)
        {
            miShowLineNumbers.IsChecked = !miShowLineNumbers.IsChecked;
            this.IsShowLineNumbers = miShowLineNumbers.IsChecked;
            App.ConfigManager.Set("IsShowLineNumbers", this.IsShowLineNumbers.ToString());
        }

        private void miShowTitleInWorkspaceManager_Click(object sender, RoutedEventArgs e)
        {
            SwitchShowTitleInWorkspaceManager();
        }

        private void ckxShowTitle_Click(object sender, RoutedEventArgs e)
        {
            SwitchShowTitleInWorkspaceManager();
        }

        private void SwitchShowTitleInWorkspaceManager()
        {
            ckxShowTitle.IsChecked =
            this.miShowTitleInWorkspaceManager.IsChecked =
            this.ShowTitleInWorkspaceManager = !this.ShowTitleInWorkspaceManager;
            App.ConfigManager.Set("ShowTitleInWorkspaceManager", this.ShowTitleInWorkspaceManager.ToString());
        }

        private void ckxShowTitle_Checked(object sender, RoutedEventArgs e)
        {
            ckxShowTitle.Foreground = Brushes.Blue;
            ckxShowTitle.FontWeight = FontWeights.Bold;
        }

        private void ckxShowTitle_Unchecked(object sender, RoutedEventArgs e)
        {
            ckxShowTitle.FontWeight = FontWeights.Normal;
            ckxShowTitle.Foreground = Brushes.Black;
        }

        private void btnMinizeToNoticeIcon_Click(object sender, RoutedEventArgs e)
        {
            windowState = this.WindowState;
            this.Hide();
        }

        private void miPreviewAsSlidesByRegion_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.Region);
        }

        private void miPreviewAsSlidesByTopLevelHeader_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.TopLevelHeader);
        }

        private void miPreviewAsSlides_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.HorizontalLine);
        }

        private void miPreviewAsSlidesByDocument_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.ByDocument);
        }

        private void miPreviewAsSlidesOnlyByHeaders_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.TopLevelHeader, true);
        }

        private void miPreviewWholeDocument_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPresentateHtml(CustomMarkdownSupport.PresentateHtmlSplitterType.WholeDocument, true);
        }

        private void MetroWindow_Activated(object sender, EventArgs e)
        {
            var activedEditor = ActivedEditor;
            if (activedEditor != null)
            {
                activedEditor.EditorBase.Focus();
            }
        }

        private void miOpenChmFile_Click(object sender, RoutedEventArgs e)
        {
            var path = Globals.PathOfWorkspace;
            DirectoryInfo di = new DirectoryInfo(path);
            path = di.FullName + di.Name + ".chm";

            if (File.Exists(path) == false)
            {
                LMessageBox.Show("工作区目录下未找到编译好的 CHM 文件。请先尝试创建 CHM 工程并调用 Html Help Workshop 进行编译。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (path.Contains(" "))
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{path}\"");
            }
            else
            {
                System.Diagnostics.Process.Start("explorer.exe", $"{path}");
            }
        }

        private void miOpenBranchChmFile_Click(object sender, RoutedEventArgs e)
        {
            var path = Globals.PathOfWorkspace;
            DirectoryInfo di = new DirectoryInfo(path);
            path = di.FullName + di.Name + "_.chm";

            if (File.Exists(path) == false)
            {
                LMessageBox.Show("工作区目录下未找到编译好的分支 CHM 文件。请先尝试创建分支 CHM 工程并调用 Html Help Workshop 进行编译。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (path.Contains(" "))
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{path}\"");
            }
            else
            {
                System.Diagnostics.Process.Start("explorer.exe", $"{path}");
            }
        }

        private void miHighlightingAdvance_Click(object sender, RoutedEventArgs e)
        {
            this.HighlightingSetting = MarkdownEditorBase.HighlightingType.Advance;
            miHighlightingAdvance.IsChecked = true;
            miHighlightingOnlyHeaders.IsChecked = false;
            miHighlightingNone.IsChecked = false;
            App.ConfigManager.Set("HighlightingSetting", HighlightingSetting.ToString());
        }

        private void miHighlightingOnlyHeaders_Click(object sender, RoutedEventArgs e)
        {
            this.HighlightingSetting = MarkdownEditorBase.HighlightingType.OnlyHeaders;
            miHighlightingOnlyHeaders.IsChecked = true;
            miHighlightingNone.IsChecked = false;
            miHighlightingAdvance.IsChecked = false;
            App.ConfigManager.Set("HighlightingSetting", HighlightingSetting.ToString());
        }

        private void miHighlightingNone_Click(object sender, RoutedEventArgs e)
        {
            this.HighlightingSetting = MarkdownEditorBase.HighlightingType.None;
            miHighlightingOnlyHeaders.IsChecked = false;
            miHighlightingNone.IsChecked = true;
            miHighlightingAdvance.IsChecked = false;
            App.ConfigManager.Set("HighlightingSetting", HighlightingSetting.ToString());
        }

        private void miCompilePageMenu_Click(object sender, RoutedEventArgs e)
        {
            if (CompilePageMenu == false && HtmlHeadersCollapse == HtmlHeadersCollapseType.Auto)
            {
                var result = LMessageBox.Show("【自动折叠标题】选项会导致左边栏菜单中的链接不起作用（除非用户展开这些标题）。\r\n\r\n　　真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result != MessageBoxResult.Yes) return;
            }

            this.CompilePageMenu =
            miCompilePageMenu.IsChecked = !miCompilePageMenu.IsChecked;
            App.WorkspaceConfigManager.Set("CompilePageMenu", miCompilePageMenu.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "CompilePageMenu",
                HtmlOptionText = "强制编译左边栏菜单",
            });
        }

        private void miInsertNewColumn_Click(object sender, RoutedEventArgs e)
        {
            var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (selEditor != null)
            {
                selEditor.EditorBase.InsertTableColumn();
                e.Handled = true;
            }
        }

        private void MetroWindow_Closed(object sender, EventArgs e)
        {
            // 关闭工作区之前，去除 Anchors~.txt 中重复的条目。
            AnchorsManager.Save();

            var workspaceEditingFilePath = Globals.PathOfWorkspace + "\\~.editing";
            if (File.Exists(workspaceEditingFilePath))
            {
                File.Delete(workspaceEditingFilePath);
            }

            RememberWorkspaceHtmlCompileOptions();

            WorkspaceManager.SaveWorkspaceTreeviewToXml();

            App.ConfigManager.Set("PerspectiveMode", PerspectiveMode.ToString());

            if (string.IsNullOrWhiteSpace(SetupPackageFilePath) == false && File.Exists(SetupPackageFilePath))
            {
                System.Diagnostics.Process.Start("explorer.exe", SetupPackageFilePath);
            }
        }

        private void btnTopmost_Click(object sender, RoutedEventArgs e)
        {
            this.Topmost = !this.Topmost;
            if (this.Topmost)
            {
                backgroundGridTopMost.Background = Brushes.DarkCyan;
            }
            else
            {
                backgroundGridTopMost.Background = Brushes.Transparent;
            }
        }

        public void RefreshTextInfos()
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                tbSelCharsCount.Text =
                    tbSelLinesCount.Text =
                    tbCharsCount.Text =
                    tbLinesCount.Text = "-";
                return;
            }

            var sl = ae.EditorBase.SelectionLength;
            tbSelCharsCount.Text = (sl == 0 ? "-" : sl.ToString());

            var startLineNumber = ae.EditorBase.Document.GetLineByOffset(ae.EditorBase.SelectionStart).LineNumber;
            var endLineNumber = ae.EditorBase.Document.GetLineByOffset(ae.EditorBase.SelectionStart + ae.EditorBase.SelectionLength).LineNumber;
            tbSelLinesCount.Text = (endLineNumber - startLineNumber + 1).ToString();

            var cc = ae.EditorBase.Document.TextLength;
            tbCharsCount.Text = (cc == 0 ? "-" : cc.ToString());

            var lc = ae.EditorBase.Document.LineCount;
            tbLinesCount.Text = (lc == 0 ? "-" : lc.ToString());
        }

        private void btnTextInfos_Click(object sender, RoutedEventArgs e)
        {
            //文档总字符数
            //英文字符数
            //标点符号数
            //中文字符数
            //空白字符数
            //其它字符数

            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.Show("当前未打开文档！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var s = ae.EditorBase.Document.Text;

            int enLetterChars = 0;
            int chLetterChars = 0;
            int enNumbers = 0;
            int chNumbers = 0;
            int blankChars = 0;
            int chChars = 0;

            int elseChars = 0;

            foreach (char c in s)
            {
                if (c >= 'a' && c <= 'z') { enLetterChars++; continue; }
                if (c >= 'A' && c <= 'Z') { enLetterChars++; continue; }
                if (c >= 'ａ' && c <= 'ｚ') { chLetterChars++; continue; }
                if (c >= 'Ａ' && c <= 'Ｚ') { chLetterChars++; continue; }
                if (c >= '0' && c <= '9') { enNumbers++; continue; }
                if (c >= '０' && c <= '９') { chNumbers++; continue; }
                if (c == ' ' || c == '\t' || c == '　') { blankChars++; continue; }

                if (c >= 0x4e00 && c <= 0x9fbb) { chChars++; continue; }
            }

            elseChars = s.Length - enLetterChars - chLetterChars - enNumbers - chNumbers - blankChars - chChars;

            LMessageBox.Show($"文档总长：{s.Length}\r\n" +
                "-------------------\r\n" +
                $"半角英文：{enLetterChars}\r\n" +
                $"全角英文：{chLetterChars}\r\n" +
                "-------------------\r\n" +
                $"中文字符：{chChars}\r\n" +
                "-------------------\r\n" +
                $"半角数字：{enNumbers}\r\n" +
                $"全角数字：{chNumbers}\r\n" +
                "-------------------\r\n" +
                $"空白字符：{blankChars}\r\n" +
                $"其它字符：{elseChars}",
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void bdAutoWrap_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            miTextWrap_Click(sender, e);
        }

        private void btnRefreshOutLine_Click(object sender, RoutedEventArgs e)
        {
            RefreshOutLines();
            if (tcManagerPanels.SelectedItem != tiOutLine)
                tcManagerPanels.SelectedItem = tiOutLine;
        }

        private void RefreshOutLines()
        {
            //string keyWord = @"^ {0,3}#{1,6}.*$";
            //FindHeaders(keyWord, tvOutLine);
            tvOutLine.Items.Clear();

            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null)
            {
                tvOutLine.Items.Add(new TreeViewItem() { Header = "...<未打开文档>..." });
                return;
            }

            Regex regex = new Regex(@"^ {0,3}[#＃]{1,6}.*$", RegexOptions.Multiline);
            var matches = regex.Matches(efi.EditorBase.Text);

            List<TreeViewItem> list = new List<TreeViewItem>();

            foreach (Match match in matches)
            {
                if (match.Success)
                {
                    var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    list.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                        match.Index - line.Offset, match.Length, lineText, null, FindLineTreeViewItem.ItemType.Header)
                    {
                        IsExpanded = true,
                        Tag = GetHeaderLevel(match.Value).ToString(),
                    });
                }
            }

            if (list.Count <= 0)
            {
                tvOutLine.Items.Add(new TreeViewItem() { Header = "...<此文档中未找到大纲>..." });
                return;
            }

            FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName)
            {
                IsExpanded = true,
                Tag = "0",
            };

            list.Insert(0, fdi);

            TreeViewItem parentItem = fdi;
            TreeViewItem previewItem = fdi;
            int parentItemLevel = 0;
            for (int i = 1; i < list.Count; i++)//第一个不算。
            {
                TreeViewItem ti = list[i];
                int tiLevel = int.Parse(ti.Tag as string);
                if (tiLevel == parentItemLevel + 1)
                {
                    parentItem.Items.Add(ti);
                }
                else if (tiLevel > parentItemLevel + 1)
                {
                    parentItem = previewItem;
                    parentItemLevel = parentItemLevel + 1;
                    parentItem.Items.Add(ti);
                }
                else if (tiLevel <= parentItemLevel)
                {
                    for (int j = list.IndexOf(ti) - 1; j >= 0; j--)
                    {
                        int liLevel = int.Parse(list[j].Tag as string);
                        if (liLevel < tiLevel)
                        {
                            parentItem = list[j];
                            parentItemLevel = liLevel;
                            break;
                        }
                    }

                    parentItem.Items.Add(ti);
                }
                previewItem = ti;
            }

            tvOutLine.Items.Add(fdi);
        }

        private int GetHeaderLevel(string headerText)
        {
            if (string.IsNullOrWhiteSpace(headerText)) return 0;
            Regex regex = new Regex(@"^ {0,3}[#＃]{1,6}.*$");
            var match = regex.Match(headerText);
            if (match.Success == false) return 0;

            int x = 0;
            foreach (char c in match.Value)
            {
                if (c == '#' || c == '＃')
                {
                    x++;
                }
            }
            if (x > 6) x = 6;//最大六级

            return x;
        }

        private void miPresentatorMode_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPresentation;
        }

        private void btnMaxImagePreviewArea_Click(object sender, RoutedEventArgs e)
        {
            if (btnMaxImagePreviewArea.Tag.ToString() == "maxsized")
            {
                //恢复正常的演示模式比例
                gsResourcePreview.Visibility =
                  btnResetLeftToolbarLayout.Visibility = Visibility.Visible;

                rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
                tcResourcePreviewArea.Margin = new Thickness(0, 10, 0, 10);
                rdResourcePreviewArea.Height = new GridLength(460, GridUnitType.Pixel);

                btnMaxImagePreviewArea.Tag = "normal";
                btnMaxImagePreviewArea.LayoutTransform = null;
            }
            else
            {
                //最大化图像演示区域
                if (rdMainMenuArea.ActualHeight > 0)
                {
                    cmbPerspective.SelectedIndex = (int)Perspective.EditingAndPresentation;
                }

                gsResourcePreview.Visibility =
                    btnResetLeftToolbarLayout.Visibility = Visibility.Hidden;

                rdLeftToolsTop.Height = new GridLength(0);
                cdLeftToolsArea.Width = new GridLength(800, GridUnitType.Pixel);
                tcResourcePreviewArea.Margin = new Thickness(0, 0, 0, 10);
                rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

                btnMaxImagePreviewArea.Tag = "maxsized";
                btnMaxImagePreviewArea.LayoutTransform = new RotateTransform()
                {
                    Angle = 180,
                    CenterX = 0.5,
                    CenterY = 0.5
                };
            }
        }

        private void btnFindAndCopyText_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var keyWord = cmbFindText.Text;

                var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
                if (efi != null)
                {
                    Regex regex = GetRegEx(keyWord, true, false);
                    var matches = regex.Matches(efi.EditorBase.Text);
                    StringBuilder sb = new StringBuilder();
                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                            var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                            sb.Append(match.Value);
                            sb.Append("\r\n");
                        }
                    }
                    var result = sb.ToString();
                    if (string.IsNullOrEmpty(result))
                    {
                        LMessageBox.Show("未找到符合条件的文本。请检查搜索关键词或正则表达式。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else
                    {
                        Clipboard.SetData(DataFormats.Text, sb.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void btnCollapseFindAndReplacePanel_Click(object sender, MouseButtonEventArgs e)
        {
            SwitchFindAndReplacePanelToggle();
        }

        public Perspective PerspectiveMode
        {
            get
            {
                if (cmbPerspective.SelectedItem == null) return Perspective.Normal;
                var selectedItem = cmbPerspective.SelectedItem as ComboBoxItem;
                if (selectedItem == null) return Perspective.Normal;
                Perspective i = (Perspective)Enum.Parse(typeof(Perspective), selectedItem.Tag.ToString());
                return i;
            }
        }

        /// <summary>
        /// 切换透视图方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbPerspective_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cmbPerspective.SelectedItem == null) return;
            var selectedItem = cmbPerspective.SelectedItem as ComboBoxItem;
            if (selectedItem == null) return;
            Perspective perspective = (Perspective)Enum.Parse(typeof(Perspective), selectedItem.Tag.ToString());

            RefreshPersective(perspective);

            //记录下关闭程序前的透视图模式，下次启动时恢复。
            App.ConfigManager.Set("PerspectiveMode", PerspectiveMode.ToString());
        }

        /// <summary>
        /// 切换透视图。
        /// </summary>
        /// <param name="perspective"></param>
        private void RefreshPersective(Perspective perspective)
        {
            switch (perspective)
            {
                case Perspective.MiniMode:
                case Perspective.VerticalMode:
                    {
                        foreach (UIElement ue in wcRightCommands.Items)
                        {
                            if (ue == cmbPerspective) continue;
                            ue.Visibility = Visibility.Collapsed;
                        }
                        break;
                    }
                default:
                    {
                        foreach (UIElement ue in wcRightCommands.Items)
                        {
                            if (ue == cmbPerspective) continue;
                            ue.Visibility = Visibility.Visible;
                        }
                        break;
                    }
            }

            switch (perspective)
            {
                case Perspective.VerticalMode:
                    {
                        rdTrdPreviewArea.MinHeight = 5;
                        break;
                    }
                default:
                    {
                        rdTrdPreviewArea.MinHeight = 0;
                        break;
                    }
            }

            switch (perspective)
            {
                case Perspective.Normal:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = true;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                            this.ShowCloseButton =
                            this.ShowMinButton =
                            this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0, GridUnitType.Auto);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
                        cdRightToolsArea.Width =
                            cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height =
                            rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        tcManagerPanels.SelectedItem = tiWorkspace;  //工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;  //帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiColorCanvas;  //调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Visible;
                        break;
                    }
                case Perspective.All:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = true;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0, GridUnitType.Auto);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
                        cdRightToolsArea.Width =
                            cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                        rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

                        tcManagerPanels.SelectedItem = tiWorkspace;  //工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;  //帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiColorCanvas;  //调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Visible;
                        break;
                    }
                case Perspective.Editing:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = true;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        //与 Normal 相比，这种启动时自动折叠右工具栏。
                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0, GridUnitType.Auto);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(0);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height =
                            rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        tcManagerPanels.SelectedItem = tiWorkspace;  // 工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;  // 帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiColorCanvas;  // 调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Visible;
                        break;
                    }
                case Perspective.FullScreenEditing:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = false;
                        this.ShowTitleBar = false;
                        this.ShowIconOnTitleBar =
                            this.ShowCloseButton =
                            this.ShowMinButton =
                            this.ShowMaxRestoreButton = false;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Collapsed;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Maximized;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        //如果是临时切换，最好不动。
                        tcManagerPanels.SelectedItem = tiWorkspace;  // 工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;  // 帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiColorCanvas;  // 调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.EditingAndPreview:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = true;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);

                        tcRightToolBar.SelectedItem = tiHtmlPreview;  // Html预览选项卡

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        //如果是临时切换，最好不动。
                        //tcManagerPanels.SelectedItem = tiWorkspace;  //  工作区管理器选项卡
                        //tcResourcePreviewArea.SelectedItem = tiColorCanvas;  //调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Visible;
                        break;
                    }
                case Perspective.FullScreenPreview:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = false;
                        this.ShowTitleBar = false;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = false;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Collapsed;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Maximized;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(0, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);

                        rdResourcePreviewArea.Height = new GridLength(0);
                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        tcRightToolBar.SelectedItem = tiHtmlPreview;  // Html预览选项卡
                        //如果是临时切换，最好不动。
                        //tcManagerPanels.SelectedItem = tiWorkspace; // 工作区管理器选项卡
                        //tcResourcePreviewArea.SelectedItem = tiColorCanvas;  // 调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Collapsed;
                        break;
                    }
                case Perspective.EditingAndPresentation:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = true;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        ShowPresentatorMode();

                        bdStatusBar.Visibility = Visibility.Visible;
                        break;
                    }
                case Perspective.CompareMode:
                    {
                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = true;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        this.MinWidth = 780; this.MinHeight = 560;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图外，其它布局默认都不使用总在最前。
                        if (this.Topmost == true)
                        {
                            this.Topmost = false;
                            backgroundGridTopMost.Background = Brushes.Transparent;
                        }

                        rdTopToolbarArea.Height =
                            rdMainMenuArea.Height = new GridLength(0);
                        mainToolBar.Visibility = Visibility.Visible;
                        bdFind.Visibility =
                            mainToolBarTray.Visibility = Visibility.Visible;

                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);
                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        bdStatusBar.Visibility = Visibility.Visible;
                        break;
                    }
                case Perspective.MiniMode:
                case Perspective.VerticalMode:  // 纵向模式与迷你模式十分接近
                    {
                        // 注意，关于标题右命令列表的显示/隐藏不在这里处理，而在这个方法头部。

                        this.tcManagerPanels.ShowHeaderPanel =
                            this.tcRightToolBar.ShowHeaderPanel =
                            this.mainTabControl.ShowHeaderPanel = false;
                        this.ShowTitleBar = true;
                        this.ShowIconOnTitleBar =
                             this.ShowCloseButton =
                             this.ShowMinButton =
                             this.ShowMaxRestoreButton = true;
                        this.RightWindowCommands.Visibility =
                            this.LeftWindowCommands.Visibility = Visibility.Visible;

                        //与全屏编辑模式不同，迷你模式适合用来摘抄文本，所以窗口更小且总在最前
                        this.MinWidth = 120; this.MinHeight = 90;
                        this.Width = 600; this.Height = 300;
                        this.windowState = WindowState.Normal;

                        //除 Mini 模式的透视图默认采用“总在最前”外，其它模式都默认正常。
                        if (this.Topmost == false)
                        {
                            this.Topmost = true;
                            backgroundGridTopMost.Background = Brushes.DarkCyan;
                        }

                        bdFind.Visibility = Visibility.Collapsed;

                        if (pyScriptsToolBar.Items.Count <= 0 && workspacePyScriptsToolBar.Items.Count <= 0)
                        {
                            rdTopToolbarArea.Height = new GridLength(0);
                            mainToolBar.Visibility = Visibility.Collapsed;
                            mainToolBarTray.Visibility = Visibility.Collapsed;     // 只有迷你模式才需要折叠主工具栏。
                        }
                        else
                        {
                            rdTopToolbarArea.Height = new GridLength(0, GridUnitType.Auto);
                            mainToolBarTray.Visibility = Visibility.Visible;
                            mainToolBar.Visibility = Visibility.Collapsed;
                            pyScriptsToolBar.Visibility = (pyScriptsToolBar.Items.Count <= 0) ? Visibility.Collapsed : Visibility.Visible;
                            workspacePyScriptsToolBar.Visibility = (workspacePyScriptsToolBar.Items.Count <= 0) ? Visibility.Collapsed : Visibility.Visible;
                        }

                        rdMainMenuArea.Height = new GridLength(0);
                        cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Pixel);
                        cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
                        cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                        rdFindAndReplace.Height = new GridLength(0);
                        rdResourcePreviewArea.Height = new GridLength(0);

                        rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);

                        //如果是临时切换，最好不动。
                        tcManagerPanels.SelectedItem = tiWorkspace;  // 工作区管理器选项卡
                        tcRightToolBar.SelectedItem = tiHelp;  // 帮助文档选项卡
                        tcResourcePreviewArea.SelectedItem = tiColorCanvas;  // 调色板选项卡

                        //tiHtmlSegmentPreview.Visibility = Visibility.Collapsed;
                        bdStatusBar.Visibility = Visibility.Collapsed;
                        break;
                    }
            }

            switch (perspective)
            {
                case Perspective.VerticalMode:
                    {
                        this.Height = 720;
                        this.Width = 405;   // h:w = 16:9

                        this.Left = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width - 405;
                        this.Top = (System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height - 720) / 2;
                        // 定位到主屏幕工作区右侧边垂直中间位置。

                        gsLeft.Visibility
                            = gsRight.Visibility
                            = btnCollapseLeftToolArea.Visibility
                            = btnCollapseRightToolArea.Visibility
                            = Visibility.Hidden;
                        if (mainGrid.Children.Contains(tcRightToolBar))
                        {
                            mainGrid.Children.Remove(tcRightToolBar);
                        }
                        if (gridMainEdiorArea.Children.Contains(tcRightToolBar) == false)
                        {
                            gridMainEdiorArea.Children.Add(tcRightToolBar);
                            Grid.SetColumn(tcRightToolBar, 0);
                            Grid.SetRow(tcRightToolBar, 0);
                        }
                        rdTrdPreviewArea.Height = new GridLength(1, GridUnitType.Star);
                        gsVerticalMode.Visibility = Visibility.Visible;

                        tcRightToolBar.Margin = new Thickness(10, 10, 10, 5);
                        mainTabControl.Margin = new Thickness(10, 5, 10, 10);
                        break;
                    }
                case Perspective.MiniMode:
                    {
                        this.Left = (System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width - this.Width) / 2;
                        this.Top = (System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height - this.Height) / 2;
                        // 定位到主屏幕工作区中间位置。

                        gsLeft.Visibility
                            = gsRight.Visibility
                            = btnCollapseLeftToolArea.Visibility
                            = btnCollapseRightToolArea.Visibility
                            = Visibility.Hidden;
                        if (gridMainEdiorArea.Children.Contains(tcRightToolBar))
                        {
                            gridMainEdiorArea.Children.Remove(tcRightToolBar);
                        }
                        if (mainGrid.Children.Contains(tcRightToolBar) == false)
                        {
                            mainGrid.Children.Add(tcRightToolBar);
                            Grid.SetColumn(tcRightToolBar, 2);
                            Grid.SetRow(tcRightToolBar, 3);
                        }
                        rdTrdPreviewArea.Height = new GridLength(0);
                        gsVerticalMode.Visibility = Visibility.Hidden;

                        tcRightToolBar.Margin = new Thickness(10, 10, 10, 10);
                        mainTabControl.Margin = new Thickness(10, 10, 10, 10);
                        break;
                    }
                default:
                    {
                        this.Left = (System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width - this.Width) / 2;
                        this.Top = (System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height - this.Height) / 2;
                        // 定位到主屏幕工作区中间位置。

                        gsLeft.Visibility
                            = gsRight.Visibility
                            = btnCollapseLeftToolArea.Visibility
                            = btnCollapseRightToolArea.Visibility
                            = Visibility.Visible;
                        if (gridMainEdiorArea.Children.Contains(tcRightToolBar))
                        {
                            gridMainEdiorArea.Children.Remove(tcRightToolBar);
                        }
                        if (mainGrid.Children.Contains(tcRightToolBar) == false)
                        {
                            mainGrid.Children.Add(tcRightToolBar);
                            Grid.SetColumn(tcRightToolBar, 2);
                            Grid.SetRow(tcRightToolBar, 3);
                        }
                        rdTrdPreviewArea.Height = new GridLength(0);
                        gsVerticalMode.Visibility = Visibility.Hidden;

                        tcRightToolBar.Margin = new Thickness(10, 10, 10, 10);
                        mainTabControl.Margin = new Thickness(10, 10, 10, 10);
                        break;
                    }
            }
        }

        /// <summary>
        /// 开启“演讲者模式”。适用于“边写边讲”的情况：
        /// 左上角显示文档提纲（由六级标题组成，不自动同步）、左下角显示可能要预览的图像、右侧显示Markdown 文档编辑区。
        /// </summary>
        private void ShowPresentatorMode()
        {
            rdTopToolbarArea.Height =
                rdMainMenuArea.Height = new GridLength(0);
            mainToolBar.Visibility = Visibility.Visible;
            bdFind.Visibility =
                mainToolBarTray.Visibility = Visibility.Visible;

            cdLeftToolsArea.Width = new GridLength(
                Math.Min(this.Width / 2, Math.Max(460, ((this.WindowState == WindowState.Maximized) ? (SystemParameters.PrimaryScreenWidth / 2 - 20) : (this.Width / 2 - 20)))),
                GridUnitType.Pixel);
            cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
            cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
            rdFindAndReplace.Height = new GridLength(0);

            rdLeftToolsTop.Height =
                rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

            tcManagerPanels.SelectedItem = tiOutLine;//大纲选项卡
            tcResourcePreviewArea.SelectedItem = tiImagePreview;//图像文件预览选项卡

            //如果是临时切换，最好不动。
            //tcRightToolBar.SelectedItem = tiHelp;//帮助文档选项卡

            //tiHtmlSegmentPreview.Visibility = Visibility.Visible;

            tcManagerPanels.SelectedItem = tiOutLine;
            btnResetLeftToolbarLayout.Visibility = Visibility.Visible;

            tcRightToolBar.SelectedIndex = 1;
            var activeEditor = ActivedEditor;
            if (activeEditor != null)
            {
                activeEditor.EditorBase.UpdateLayout();
                cmbSearchArea2.SelectedIndex = 0;
                btnRefreshOutLine_Click(null, null);
            }

            //折叠所有单行的图像链接文本、二维文字表、自定义折叠区等。
            if (SupportFolding)
            {
                var efi = ActivedEditor;
                if (efi != null)
                {
                    foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
                    {
                        var folding = i.Tag as ICSharpCode.AvalonEdit.Folding.NewFolding;
                        if (folding != null && folding.IsSpecial)
                        {
                            i.IsFolded = true;
                        }
                    }
                }
            }

            //tiHtmlSegmentPreview.Visibility = Visibility.Visible;
        }

        private void miExitFullScreenC_Click(object sender, RoutedEventArgs e)
        {
            cmbPerspective.SelectedIndex = (int)Perspective.Editing;
        }

        private void tbSegmentPreviewHeader_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount != 2) return;
            PreviewMarkdownSegment();
        }

        public void PreviewMarkdownSegment()
        {
            var editor = ActivedEditor;
            if (editor == null)
            {
                LMessageBox.Show("请先打开一个文档！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var segmentText = "";

            var fstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart);
            var lstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart + editor.EditorBase.SelectionLength);

            segmentText = editor.EditorBase.Document.GetText(fstLine.Offset, lstLine.EndOffset - fstLine.Offset);

            if (string.IsNullOrWhiteSpace(segmentText))
            {
                LMessageBox.Show("没选中什么可以预览的内容！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            editor.CompileAndPresentateHtml(
                                 CustomMarkdownSupport.PresentateHtmlSplitterType.TextSegment,
                                 false, segmentText);
        }

        private void miCompileCleanHeaderLines_Click(object sender, RoutedEventArgs e)
        {
            miCompileCleanHeaderLines.IsChecked = !miCompileCleanHeaderLines.IsChecked;
            this.CleanHeaders = miCompileCleanHeaderLines.IsChecked;
            App.WorkspaceConfigManager.Set("CleanHeaders", miCompileCleanHeaderLines.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "CompileCleanHeaderLines",
                HtmlOptionText = "标题编译为简洁格式",
            });
        }

        private void miTryToCollapseCustomRegion_Click(object sender, RoutedEventArgs e)
        {
            miTryToCollapseCustomRegion.IsChecked = !miTryToCollapseCustomRegion.IsChecked;
            this.TryToCollapseCustomRegion = miTryToCollapseCustomRegion.IsChecked;
            App.WorkspaceConfigManager.Set("TryToCollapseCustomRegion", miTryToCollapseCustomRegion.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "TryToCollapseCustomRegion",
                HtmlOptionText = "尝试折叠自定义折叠区",
            });
        }

        private void MetroWindow_Deactivated(object sender, EventArgs e)
        {
            foreach (var ue in this.mainTabControl.Items)
            {
                MarkdownEditor edit = ue as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.PopupToolBar.IsOpen = false;
            }


        }

        private void SwitchPopupToolbarEnabled_Click(object sender, RoutedEventArgs e)
        {
            this.IsPopupContextToolbarEnabled = !this.IsPopupContextToolbarEnabled;
            miIsPopupContextToolbarEnabled.IsChecked = this.IsPopupContextToolbarEnabled;
            App.ConfigManager.Set("IsPopupContextToolbarEnabled", miIsPopupContextToolbarEnabled.IsChecked.ToString());
        }

        private void segmentPreviewWebBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
        {
            //设置片段预览区的高度。
            IHTMLDocument2 doc = segmentPreviewWebBrowser.Document as IHTMLDocument2;
            double zoom = mainTabControl.FontSize / 16;
            //doc.parentWindow.execScript($"document.body.style.zoom={zoom}");//初始缩放比例
            //doc.parentWindow.execScript($"document.body.style.fontSize='{mainTabControl.FontSize}px'");
            //使用zoom是可以实现缩放，但是页面过宽。
            //看起来还是应该放大字号

            //放在后面可以自动取出准确点的scrollHeight值。
            double height = (int)segmentPreviewWebBrowser.InvokeScript("getHeight");
            height *= zoom;
            height += 88;

            //if (segmentPreviewWebBrowser.ActualHeight > height) return;
            var gdl = new GridLength(
                Math.Max(0, Math.Min(Globals.MainWindow.leftToolBarGrid.ActualHeight, height)), GridUnitType.Pixel);
            //rdLeftToolsTop.MaxHeight = gdl.Value;//leftToolBarGrid.ActualHeight;
            //rdLeftToolsTop.MaxHeight = leftToolBarGrid.ActualHeight - 58;
            rdLeftToolsTop.Height = gdl;
            rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);

            if (segmentPreviewWebBrowser.Visibility != Visibility.Visible) segmentPreviewWebBrowser.Visibility = Visibility.Visible;
        }

        public WebBrowser PreviewWebBrowser
        {
            get { return previewFrame.Content as WebBrowser; }
        }

        public Frame PreviewFrame
        {
            get { return previewFrame; }
        }

        /// <summary>
        /// Inject javascript code to previewFrame(webBrowser).
        /// </summary>
        public bool InjectJSCode(string jscode)
        {
            var web = PreviewWebBrowser;
            if (web == null) return false;
            try
            {
                mshtml.HTMLDocument htmlDoc = web.Document as mshtml.HTMLDocument;
                var head = htmlDoc.getElementsByTagName("head").Cast<HTMLHeadElement>().First();
                var script = (IHTMLScriptElement)htmlDoc.createElement("script");
                script.text = jscode;
                head.appendChild((IHTMLDOMNode)script);
                return true;
            }
            catch { return false; }
        }

        /// <summary>
        /// Inject javascript file link to previewFrame(webBrowser).
        /// </summary>
        public bool InjectJSFile(string jsFileRelativePath)
        {
            var web = PreviewWebBrowser;
            if (web == null) return false;
            try
            {
                mshtml.HTMLDocument htmlDoc = web.Document as mshtml.HTMLDocument;
                var head = htmlDoc.getElementsByTagName("head").Cast<HTMLHeadElement>().First();
                var script = (IHTMLScriptElement)htmlDoc.createElement("script");
                script.src = jsFileRelativePath;
                head.appendChild((IHTMLDOMNode)script);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Get changed(by javascript) html text from previewFrame.
        /// </summary>
        public string GetHtml()
        {
            var header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
                         "<html xmlns=\"http://www.w3.org/1999/xhtml\" >\n";
            var tail = "\n</html>";
            var webBrowser = previewFrame.Content as WebBrowser;
            if (webBrowser == null) { return "未找到 WebBrowser ！请检查预览区中是否正在预览 Html 网页文档！"; }
            try
            {
                var html = webBrowser.InvokeScript("getHtml") as string;
                if (string.IsNullOrWhiteSpace(html))
                {
                    return "没有在预览区中找到 Html 文档的内容文本。请检查预览区中是否正在预览 Html 网页文档或 Html 文档的内容是否正确！";
                }
                return $"{header}{html}{tail}";
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        /// <summary>
        /// Run javascript code in previewFrame(webBrowser).
        /// </summary>
        public string RunJS(string jsCode)
        {
            if (string.IsNullOrWhiteSpace(jsCode)) return $"{nameof(jsCode)}不能为空白或 null。";
            var webBrowser = previewFrame.Content as WebBrowser;
            if (webBrowser == null) { return "未找到 WebBrowser ！请检查预览区中是否正在预览 Html 网页文档！"; }
            try
            {
                return webBrowser.InvokeScript(jsCode) as string;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        private void btnSaveActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            miSave_Click_1(sender, e);
        }

        private void btnPreviewSegmentHtml_Click(object sender, RoutedEventArgs e)
        {
            PreviewMarkdownSegment();
        }

        /// <summary>
        /// 向当前编辑位置粘贴一道或多道选择题。
        /// </summary>
        public void PasteOptionQuestion()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            try
            {
                var text = Clipboard.GetText(TextDataFormat.Text);
                if (string.IsNullOrWhiteSpace(text)) return;

                //尝试分解试题
                var textEditor = editor.EditorBase;
                var result = OptionQuestionProcesser.ConvertToExamText(text.Replace("【解析】\r\n【详解】\r\n", "【解析】"));
                if (string.IsNullOrWhiteSpace(result))
                {
                    return;//还是不加MessageBox了，太烦人了。
                }
                textEditor.BeginChange();
                textEditor.Document.Replace(textEditor.SelectionStart, textEditor.SelectionLength, result);
                textEditor.EndChange();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message);
            }
        }

        private void miTryToPasteSelectExam_Click(object sender, RoutedEventArgs e)
        {
            PasteOptionQuestion();
        }

        private void btnPasetSelectExam_Click(object sender, RoutedEventArgs e)
        {
            PasteOptionQuestion();
        }

        private void miConvertLinesToCharCode_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null)
            {
                LMessageBox.Show("请先打开一个文档并选中一些文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var selText = activeEditor.EditorBase.SelectedText;
            if (string.IsNullOrEmpty(selText))
            {
                LMessageBox.Show("请在当前文档中先选中一些文本！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            StringBuilder sb = new StringBuilder();
            foreach (var c in selText)
            {
                var header = $" {c}";
                switch (c)
                {
                    case ' ': header = "\\s"; break;
                    case '　': header = "\\S"; break;
                    case '\r': header = "\\r"; break;
                    case '\n': header = "\\n"; break;
                    case '\t': header = "\\t"; break;
                }

                sb.Append($"     {header}，十进制码：{((int)c).ToString("D10")}，十六进制码：{CharToUnicode.CharacterToCoding(c)}\r\n");
            }

            activeEditor.EditorBase.BeginChange();
            activeEditor.EditorBase.SelectedText = $"\r\n{sb.ToString()}\r\n";
            activeEditor.EditorBase.EndChange();
        }

        private void btnMoveEntryToTopStart_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index < 1) return;

            int topIndex = -1;
            for (int m = index - 1; m >= 0; m--)
            {
                WorkspaceTreeViewItem ti = parentItem.Items[m] as WorkspaceTreeViewItem;
                if (ti == null)
                {
                    topIndex = m + 1;
                    break;
                }

                if (ti.IsResourceDirectory)
                {
                    topIndex = m + 1;
                    break;
                }
            }

            if (topIndex < 0)
            {
                //如果没有资源文件夹，直接放到头部即可。
                parentItem.Items.RemoveAt(index);
                parentItem.Items.Insert(0, item);
                item.IsSelected = true;
                return;
            }

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(topIndex, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToPreview_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index < 1) return;

            //如果前一个同级条目是资源文件目录，则直接返回。
            //资源文件夹条目总是在顶部的。
            WorkspaceTreeViewItem previewItem = null;
            if (index >= 1)
            {
                previewItem = parentItem.Items[index - 1] as WorkspaceTreeViewItem;
            }

            if (previewItem == null) return;
            if (previewItem.IsResourceDirectory) return;

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(index - 1, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToNext_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index >= parentItem.Items.Count - 1) return;

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(index + 1, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToBottomEnd_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;
            if (parentItem.Items.Count <= 1) return;
            var index = parentItem.Items.IndexOf(item);
            if (index < 0 || index >= parentItem.Items.Count - 1) return;

            parentItem.Items.RemoveAt(index);
            parentItem.Items.Insert(parentItem.Items.Count, item);

            item.IsSelected = true;
        }

        private void btnMoveEntryToLeft_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null) return;

            if (item.ItemType != WorkspaceTreeViewItem.Type.File &&
                item.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null) return;

            if (parentItem.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            //左移是成为父元素的兄弟。

            var grandItem = parentItem.ParentWorkspaceTreeViewItem;
            if (grandItem == null || grandItem.ItemType != WorkspaceTreeViewItem.Type.Folder) return;//根元素的直接下级不能再向左移动。
            var parentIndex = grandItem.Items.IndexOf(parentItem);
            if (parentIndex < 0) return;

            parentItem.Items.Remove(item);
            var newItem = new WorkspaceTreeViewItem(item.Title, item.FullPath, item.StatuHeader,
                item.StatuTail, item.ToolTip as string, item.IsHomePage, item.ItemType, item.ChmImageIndex, item.MasterWindow);
            grandItem.Items.Insert(parentIndex + 1, newItem);

            for (int i = item.Items.Count - 1; i >= 0; i--)
            {
                var ii = item.Items[i];
                item.Items.RemoveAt(i);
                newItem.Items.Insert(0, ii);
            }

            newItem.IsSelected = true;
        }

        private void btnMoveEntryToRight_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null) return;
            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null || item == tvWorkDirectory.Items[0]) return;     //根元素不能右移。

            var parentItem = item.ParentWorkspaceTreeViewItem;
            if (parentItem == null || parentItem.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            //右移是指移动到前一个兄弟的下级。

            var index = parentItem.Items.IndexOf(item);
            if (index <= 0) return;//前面没有兄弟元素，无法右移。

            WorkspaceTreeViewItem previewFolderItem = null;
            for (int i = index - 1; i >= 0; i--)
            {
                var itemx = parentItem.Items[i] as WorkspaceTreeViewItem;
                if (itemx == null) continue;
                if (itemx.ItemType == WorkspaceTreeViewItem.Type.Folder)
                {
                    previewFolderItem = itemx;
                    break;
                }
            }

            if (previewFolderItem == null) return;

            if (previewFolderItem.ItemType != WorkspaceTreeViewItem.Type.Folder) return;

            parentItem.Items.Remove(item);
            var newItem = new WorkspaceTreeViewItem(
                item.Title, item.FullPath, item.StatuHeader, item.StatuTail, item.ToolTip as string,
                item.IsHomePage, item.ItemType, item.ChmImageIndex, item.MasterWindow);
            previewFolderItem.Items.Add(newItem);

            for (int i = item.Items.Count - 1; i >= 0; i--)
            {
                var ii = item.Items[i];
                item.Items.RemoveAt(i);
                newItem.Items.Insert(0, ii);
            }

            newItem.IsSelected = true;
        }

        private void btnRebuildWorkspaceItemsFromDisk_Click(object sender, RoutedEventArgs e)
        {
            var result = LMessageBox.Show("此功能会清除当前工作区管理器的条目布局并重新读取整个工作区目录。\r\n\r\n　　此操作不可恢复且速度会很慢，真的要继续吗？。", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);
            if (result != MessageBoxResult.Yes) return;

            var activeEditor = ActivedEditor;

            workspaceManager.RefreshWorkspaceTreeView(activeEditor != null ? activeEditor.FullFilePath : null);
            workspaceManager.SaveWorkspaceTreeviewToXml();
        }

        private void btnCollapseMoveEntriesPanel_Click(object sender, RoutedEventArgs e)
        {
            //this.btnCollapseMoveEntriesPanel.IsChecked = !this.btnCollapseMoveEntriesPanel.IsChecked;

            if (this.btnCollapseMoveEntriesPanel.IsChecked == true)
            {
                bdMoveEntries.Visibility = Visibility.Collapsed;
                this.btnCollapseMoveEntriesPanel.Content = "◆";
                tvWorkDirectory.Margin = new Thickness(0);
            }
            else
            {
                bdMoveEntries.Visibility = Visibility.Visible;
                this.btnCollapseMoveEntriesPanel.Content = "◇";
                tvWorkDirectory.Margin = new Thickness(28, 0, 0, 0);
            }
        }

        private ReplaceWithNumberWindow replaceNumberWindow = null;

        #region 换行时要保留的前导字符串
        private string leaderTextRegsA = "";
        /// <summary>
        /// 回车换行时应保留的前导字符。这应该是个正则表达式。
        /// A 类是原文本保留的。
        /// </summary>
        public string LeaderTextRegsA
        {
            get { return this.leaderTextRegsA; }
            set { this.leaderTextRegsA = value; }
        }

        private string leaderTextRegsB = "";
        /// <summary>
        /// 回车换行时应保留的前导字符。这应该是个正则表达式。
        /// B 类会将当前行原文本头部匹配的文本转换为半角空格添加到新行头部。
        /// </summary>
        public string LeaderTextRegsB
        {
            get { return this.leaderTextRegsB; }
            set { this.leaderTextRegsB = value; }
        }

        private string replaceLeaderChar = "$";
        /// <summary>
        /// 用于替换型前导字符串的头部第一个字符。
        /// </summary>
        public string ReplaceLeaderChar
        {
            get { return this.replaceLeaderChar; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    this.replaceLeaderChar = "$";
                }
                else
                {
                    this.replaceLeaderChar = value.Substring(0, 1);//取第一个字符。
                }
            }
        }
        #endregion 换行时要保留的前导字符串

        /// <summary>
        /// 用户自定义工作区环境变量词典。
        /// </summary>
        public Dictionary<string, string> WorkspaceEnvironmentValues { get; internal set; } = new Dictionary<string, string>();

        /// <summary>
        /// 用户自定义全局（用户）环境变量词典。
        /// </summary>
        public Dictionary<string, string> UserEnvironmentValues { get; internal set; } = new Dictionary<string, string>();


        private void miReplaceWithNumber_Click(object sender, RoutedEventArgs e)
        {
            if (replaceNumberWindow == null)
            {
                replaceNumberWindow = new ReplaceWithNumberWindow()
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                };
            }

            replaceNumberWindow.Show();
        }

        private void btnSetVimKey_Click(object sender, RoutedEventArgs e)
        {
            var vimKeySelector = new VimKeySelector() { Owner = this, };
            switch (Globals.VimKey)
            {
                case Key.RightShift:
                    {
                        vimKeySelector.rbtnRightShift.IsChecked = true;
                        break;
                    }
                case Key.LeftCtrl:
                    {
                        vimKeySelector.rbtnLeftCtrl.IsChecked = true;
                        break;
                    }
                case Key.RightCtrl:
                    {
                        vimKeySelector.rbtnRightCtrl.IsChecked = true;
                        break;
                    }
                case Key.LeftShift:
                    {
                        vimKeySelector.rbtnLeftShift.IsChecked = true;
                        break;
                    }
                default:
                    {
                        vimKeySelector.rbtnNoneKey.IsChecked = true;
                        break;
                    }
            }

            if (vimKeySelector.ShowDialog() == true)
            {
                switch (vimKeySelector.VimKey)
                {
                    case Key.RightShift:
                        {
                            Globals.VimKey = Key.RightShift;
                            tbVimKeyText.Text = "RShift";
                            App.ConfigManager.Set("VimKey", "RightShift");
                            break;
                        }
                    case Key.LeftCtrl:
                        {
                            Globals.VimKey = Key.LeftCtrl;
                            tbVimKeyText.Text = "LCtrl";
                            App.ConfigManager.Set("VimKey", "LeftCtrl");
                            break;
                        }
                    case Key.RightCtrl:
                        {
                            Globals.VimKey = Key.RightCtrl;
                            tbVimKeyText.Text = "RCtrl";
                            App.ConfigManager.Set("VimKey", "RightCtrl");
                            break;
                        }
                    case Key.LeftShift:
                        {
                            Globals.VimKey = Key.LeftShift;
                            tbVimKeyText.Text = "LShift";
                            App.ConfigManager.Set("VimKey", "LeftShift");
                            break;
                        }
                    default:
                        {
                            Globals.VimKey = Key.None;
                            tbVimKeyText.Text = "None";
                            App.ConfigManager.Set("VimKey", "None");
                            break;
                        }
                }
            }
        }

        private void miCopyFindedTaskList_Click(object sender, RoutedEventArgs e)
        {
            if (tvTaskList.Items.Count <= 0)
            {
                LMessageBox.Show("当前没有查找到任何任务列表！不能复制。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var fdi = tvTaskList.Items[0] as FindDocumentTreeViewItem;
            if (fdi == null || fdi.Items.Count <= 0)
            {
                LMessageBox.Show("当前没有查找到任何任务列表！不能复制。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var findedTaskListItemCount = 0;
            StringBuilder sb = new StringBuilder();

            sb.Append(fdi.HeaderTextBlock.Text);
            sb.Append("\r\n");

            foreach (var item in fdi.Items)
            {
                var ti = item as FindTaskListItem;
                if (ti == null) continue;

                findedTaskListItemCount++;

                sb.Append("　" + ti.LineText + "\r\n");

                foreach (var subItem in ti.Items)
                {
                    var tti = subItem as FindTaskListTimeTagItem;
                    if (tti == null) continue;

                    sb.Append("　　" + tti.LineText + "\r\n");
                }
            }

            if (findedTaskListItemCount <= 0)
            {
                LMessageBox.Show("当前没有查找到任何任务列表！不能复制。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }

            Clipboard.SetData(DataFormats.Text, sb.ToString());
        }

        private void miSetLineLeaderText_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new LineLeaderTextSettingDialog() { Owner = this, WindowStartupLocation = WindowStartupLocation.CenterOwner, };
            dlg.ShowDialog();
        }

        private void miEnableBaseMDSyntax_Click(object sender, RoutedEventArgs e)
        {
            this.EnableBaseMDSyntax =
                                    miEnableBaseMDSyntax.IsChecked = !miEnableBaseMDSyntax.IsChecked;
            App.WorkspaceConfigManager.Set("EnableBaseMDSyntax", miEnableBaseMDSyntax.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "EnableBaseMDSyntax",
                HtmlOptionText = "启用 Markdown 基本语法支持",
            });
        }

        private void tbnJoinLine_Click(object sender, RoutedEventArgs e)
        {
            TextCommandManager.JoinLines(null);
        }

        private void tbnInsertNewLine_Click(object sender, RoutedEventArgs e)
        {
            TextCommandManager.InsertNewLine(null);
        }

        #region 取控件内部某个子控件
        /// <summary>
        /// 此方法来自于：http://www.cnblogs.com/muzizongheng/p/3170850.html
        /// 作者：muzizongheng
        /// </summary>
        private static ChildItem FindVisualChildItem<ChildItem>(DependencyObject obj) where ChildItem : DependencyObject
        {
            if (null != obj)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                    if (child != null && child is ChildItem)
                        return (ChildItem)child;
                    else
                    {
                        ChildItem childOfChild = FindVisualChildItem<ChildItem>(child);
                        if (childOfChild != null)
                            return childOfChild;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// 此方法来自于：http://www.cnblogs.com/muzizongheng/p/3170850.html
        /// 作者：muzizongheng
        /// </summary>
        private static ChildItem FindVisualChildItem<ChildItem>(DependencyObject obj, string name) where ChildItem : FrameworkElement
        {
            if (null != obj)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                    if (child != null && child is ChildItem && (child as ChildItem).Name.Equals(name))
                    {
                        return (ChildItem)child;
                    }
                    else
                    {
                        ChildItem childOfChild = FindVisualChildItem<ChildItem>(child, name);
                        if (childOfChild != null && childOfChild is ChildItem && (childOfChild as ChildItem).Name.Equals(name))
                        {
                            return childOfChild;
                        }
                    }
                }
            }
            return null;
        }
        #endregion

        private void btnNewFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        private void miEditUserEnvironmentValues_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var sw = new PlainTextEditor(Globals.PathOfUserEnvironmentValuesFilePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                    Title = Globals.AppName + " - 编辑用户环境变量",
                };

                if (File.Exists(Globals.PathOfUserEnvironmentValuesFilePath))
                {
                    sw.editorBase.Text = File.ReadAllText(Globals.PathOfUserEnvironmentValuesFilePath);
                }

                if (string.IsNullOrEmpty(sw.editorBase.Text))
                {
                    sw.editorBase.Text = Properties.Resources.UserEnvironmentValues;
                }

                if (sw.ShowDialog() == true)
                {
                    LoadUserEnvironmentValues();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        private void LoadUserEnvironmentValues()
        {
            UserEnvironmentValues.Clear();

            if (File.Exists(Globals.PathOfUserEnvironmentValuesFilePath) == false) return;

            var text = File.ReadAllText(Globals.PathOfUserEnvironmentValuesFilePath);

            RegexOptions options = RegexOptions.None;
            options |= RegexOptions.Multiline;

            var reg = new Regex(@"^/\*.*?\*/", options);
            var matches = reg.Matches(text);
            if (matches.Count <= 0) return;

            List<string> pieces = new List<string>();

            int nextIndex = text.Length;
            for (int i = matches.Count - 1; i >= 0; i--)
            {
                var match = matches[i];
                pieces.Insert(0, text.Substring(match.Index, nextIndex - match.Index));
                nextIndex = match.Index;
            }

            var splitter = new string[] { "\r\n" };
            foreach (var piece in pieces)
            {
                var lines = piece.Split(splitter, StringSplitOptions.None);
                string key = null;
                StringBuilder value = new StringBuilder();
                foreach (var line in lines)
                {
                    var startIndex = line.IndexOf("/*");
                    var endIndex = line.IndexOf("*/");
                    if (startIndex >= 0 && endIndex > startIndex + 2)// /**/ 之间至少要有一个字符。
                    {
                        key = line.Substring(startIndex + 2, endIndex - startIndex - 2);
                        var indexOfMinus = key.IndexOf("-");
                        if (indexOfMinus == 0)//非法，不能以减号开头
                        {
                            key = null;
                            break;        //减号前至少要有一个字符
                        }
                        else if (indexOfMinus > 0)
                        {
                            key = key.Substring(0, indexOfMinus);
                        }

                        continue;
                    }

                    value.Append(line);
                    value.Append("\r\n");
                }

                var valueResult = value.ToString();
                if (valueResult.EndsWith("\r\n")) valueResult = valueResult.Substring(0, valueResult.Length - 2);

                if (UserEnvironmentValues.ContainsKey(key))
                {
                    UserEnvironmentValues[key] = valueResult;
                }
                else
                {
                    UserEnvironmentValues.Add(key, valueResult);
                }
            }
        }

        private void LoadWorkspaceEnvironmentValues()
        {
            WorkspaceEnvironmentValues.Clear();

            if (File.Exists(Globals.PathOfWorkspaceEnvironmentValuesFilePath) == false) return;

            var text = File.ReadAllText(Globals.PathOfWorkspaceEnvironmentValuesFilePath);

            RegexOptions options = RegexOptions.None;
            options |= RegexOptions.Multiline;

            var reg = new Regex(@"^/\*.*?\*/", options);
            var matches = reg.Matches(text);
            if (matches.Count <= 0) return;

            List<string> pieces = new List<string>();

            int nextIndex = text.Length;
            for (int i = matches.Count - 1; i >= 0; i--)
            {
                var match = matches[i];
                pieces.Insert(0, text.Substring(match.Index, nextIndex - match.Index));
                nextIndex = match.Index;
            }

            var splitter = new string[] { "\r\n" };
            foreach (var piece in pieces)
            {
                var lines = piece.Split(splitter, StringSplitOptions.None);
                string key = null;
                StringBuilder value = new StringBuilder();
                foreach (var line in lines)
                {
                    var startIndex = line.IndexOf("/*");
                    var endIndex = line.IndexOf("*/");
                    if (startIndex >= 0 && endIndex > startIndex + 2)// /**/ 之间至少要有一个字符。
                    {
                        key = line.Substring(startIndex + 2, endIndex - startIndex - 2);
                        var indexOfMinus = key.IndexOf("-");
                        if (indexOfMinus == 0)//非法，不能以减号开头
                        {
                            key = null;
                            break;        //减号前至少要有一个字符
                        }
                        else if (indexOfMinus > 0)
                        {
                            key = key.Substring(0, indexOfMinus);
                        }

                        continue;
                    }

                    value.Append(line);
                    value.Append("\r\n");
                }

                var valueResult = value.ToString();
                if (valueResult.EndsWith("\r\n")) valueResult = valueResult.Substring(0, valueResult.Length - 2);

                if (WorkspaceEnvironmentValues.ContainsKey(key))
                {
                    WorkspaceEnvironmentValues[key] = valueResult;
                }
                else
                {
                    WorkspaceEnvironmentValues.Add(key, valueResult);
                }
            }
        }

        private void miEditWorkspaceEnvironmentValues_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var sw = new PlainTextEditor(Globals.PathOfWorkspaceEnvironmentValuesFilePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                    Title = Globals.AppName + " - 编辑工作区环境变量",
                };

                if (File.Exists(Globals.PathOfWorkspaceEnvironmentValuesFilePath))
                {
                    sw.editorBase.Text = File.ReadAllText(Globals.PathOfWorkspaceEnvironmentValuesFilePath);
                }

                if (string.IsNullOrEmpty(sw.editorBase.Text))
                {
                    sw.editorBase.Text = Properties.Resources.WorkspaceEnvironmentValues;
                }

                if (sw.ShowDialog() == true)
                {
                    LoadWorkspaceEnvironmentValues();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        private void miCheckUpdate_Click(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Process.Start("explorer.exe", "https://gitee.com/lunarsf/Lunar-Markdown-Editor/attach_files");
            //不能用destUri.AbsoluteUri，因为会进行URL转码导致找不到文件;
            //不能加这个前缀"file:///" + 
        }

        private void miTutorial_Click(object sender, RoutedEventArgs e)
        {
            OpenTutorial();
        }

        private void miIronPythonForLmeTutorial_Click(object sender, RoutedEventArgs e)
        {
            OpenIronPythonForLmeTutorial();
        }

        private void miSetAsChmHomePage_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.Items.Count <= 0) return;

            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请在工作区管理器中选中个 普通目录 或 Markdown 文件条目！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (item == null)
            {
                LMessageBox.Show("此条目或其指向的文件不能设置为 CHM 工程首页！",
                   Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var rootItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
            if (rootItem != null)
            {
                List<WorkspaceTreeViewItem> homePageItemsList = new List<WorkspaceTreeViewItem>();
                FindHomePageItems(rootItem, ref homePageItemsList);
                if (homePageItemsList.Count > 0)
                {
                    foreach (var pi in homePageItemsList)
                    {
                        pi.IsHomePage = false;
                    }
                }
            }

            switch (item.ItemType)
            {
                case WorkspaceTreeViewItem.Type.MetaFile:
                case WorkspaceTreeViewItem.Type.Folder:
                case WorkspaceTreeViewItem.Type.File:
                    {
                        item.IsHomePage = true;
                        break;
                    }
                default:
                    {
                        LMessageBox.Show("此条目或其指向的文件不能设置为 CHM 工程首页！",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
            }

            WorkspaceManager.SaveWorkspaceTreeviewToXml();
        }

        private WorkspaceTreeViewItem FindFirstHomePageItem()
        {
            if (tvWorkDirectory.Items.Count <= 0) return null;

            var rootNodeItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
            if (rootNodeItem == null) return null;
            if (rootNodeItem.IsHomePage) return rootNodeItem;

            return FindHomePageItem(rootNodeItem);
        }

        private WorkspaceTreeViewItem FindHomePageItem(WorkspaceTreeViewItem nodeItem)
        {
            if (nodeItem == null) return null;

            switch (nodeItem.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        if (nodeItem.IsHomePage) return nodeItem;
                        else break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                case WorkspaceTreeViewItem.Type.MetaFile:
                    {
                        if (nodeItem.IsHomePage) return nodeItem;

                        foreach (var ue in nodeItem.Items)
                        {
                            var resultItem = FindHomePageItem(ue as WorkspaceTreeViewItem);
                            if (resultItem != null) return resultItem;
                        }
                        break;
                    }
            }

            return null;
        }

        private void FindHomePageItems(WorkspaceTreeViewItem nodeItem, ref List<WorkspaceTreeViewItem> resultList)
        {
            if (nodeItem == null || resultList == null) return;

            switch (nodeItem.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        if (nodeItem.IsHomePage) { resultList.Add(nodeItem); }
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                case WorkspaceTreeViewItem.Type.MetaFile:
                    {
                        if (nodeItem.IsHomePage) { resultList.Add(nodeItem); }
                        foreach (var ue in nodeItem.Items)
                        {
                            FindHomePageItems(ue as WorkspaceTreeViewItem, ref resultList);
                        }
                        break;
                    }
            }
        }

        private void miCancelChmHomePage_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.Items.Count <= 0) return;

            var rootItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
            if (rootItem != null)
            {
                List<WorkspaceTreeViewItem> homePageItemsList = new List<WorkspaceTreeViewItem>();
                FindHomePageItems(rootItem, ref homePageItemsList);
                if (homePageItemsList.Count > 0)
                {
                    foreach (var pi in homePageItemsList)
                    {
                        pi.IsHomePage = false;
                    }
                }
            }

            WorkspaceManager.SaveWorkspaceTreeviewToXml();
        }

        private void miGetWorkspaceStructureText_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.Items.Count <= 0) return;
            var rootItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
            if (rootItem == null) return;

            var sb = new StringBuilder();

            GetWorkspaceStructureText(rootItem, "", ref sb);
            Clipboard.SetData(DataFormats.Text, sb.ToString());
        }

        private void GetWorkspaceStructureText(WorkspaceTreeViewItem item, string prefix, ref StringBuilder sb)
        {
            if (item == null) return;
            if ((item.ItemType == WorkspaceTreeViewItem.Type.File ||
                item.ItemType == WorkspaceTreeViewItem.Type.Folder ||
                item.ItemType == WorkspaceTreeViewItem.Type.MetaFile) == false) return; //其它类型不需要。

            sb.Append(prefix);
            sb.Append(item.Title);
            sb.Append("\r\n");

            prefix += "#";
            foreach (var subItem in item.Items)
            {
                var swi = subItem as WorkspaceTreeViewItem;
                if (swi == null) continue;

                GetWorkspaceStructureText(swi, prefix, ref sb);
            }

            if (prefix.Length >= 1) prefix = prefix.Substring(0, prefix.Length - 1);
        }

        private void cmiCopyImageData_Click(object sender, RoutedEventArgs e)
        {
            var source = ImagePreview.Source as BitmapSource;
            if (source == null)
            {
                LMessageBox.Show("无图像数据可以复制！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            Clipboard.SetImage(source);
        }

        private void cmiCopyImageFilePath_Click(object sender, RoutedEventArgs e)
        {
            var wti = imagePreviewOutBorder.Tag as WorkspaceTreeViewItem;
            if (wti == null || wti.ItemType != WorkspaceTreeViewItem.Type.Image)
            {
                LMessageBox.Show("未找到对应图像文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                Clipboard.SetData(DataFormats.Text, wti.FullPath);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void btnAddSpacesAtLineHeader_Click(object sender, RoutedEventArgs e)
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            editor.EditorBase.AddCodeBlockMarksToSelectedLines();
        }

        private void btnRemoveSpacesAtLineHeader_Click(object sender, RoutedEventArgs e)
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            editor.EditorBase.RemoveCodeBlockMarksToSelectedLines();
        }

        private void btnPasetExcelDataBlockToTextTable_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var edit = ActivedEditor;
                if (edit == null) return;

                var excelText = Clipboard.GetText(TextDataFormat.Text);
                if (string.IsNullOrEmpty(excelText)) return;

                var lines = excelText.Split(new string[] { "\r\n" }, StringSplitOptions.None);
                if (lines.Length <= 0) return;

                StringBuilder sb = new StringBuilder();
                sb.Append("\n");

                foreach (var line in lines)
                {
                    if (line.Contains("\n") == false)
                    {
                        sb.Append("\r\n｜");
                        sb.Append(line.Replace("\t", "｜"));
                        sb.Append("｜");
                        continue;
                    }

                    //如果存在单元格内强制换行的情况
                    var cells = line.Split('\t');
                    var maxSubLinesCount = 1;
                    foreach (var cell in cells)
                    {
                        var spans = cell.Split('\n');
                        maxSubLinesCount = Math.Max(spans.Length, maxSubLinesCount);
                    }

                    var cellsList = new List<List<string>>();
                    for (int i = 0; i < cells.Length; i++)
                    {
                        var li = new List<string>();
                        var cellText = cells[i];
                        if (cellText.StartsWith("\"") && cellText.EndsWith("\"") && cellText.Contains("\n"))
                        {
                            cellText = cellText.Substring(1, cellText.Length - 2);
                            var spans = cellText.Split('\n');
                            foreach (var span in spans)
                            {
                                li.Add(span);
                            }
                            cellsList.Add(li);
                        }
                        else
                        {
                            cellsList.Add(new List<string>() { cellText, });
                        }
                    }

                    for (int i = 0; i < maxSubLinesCount; i++)
                    {
                        StringBuilder sb2 = new StringBuilder();
                        sb2.Append("\r\n｜");
                        for (int j = 0; j < cellsList.Count; j++)
                        {
                            var li = cellsList[j];
                            var piece = "";
                            if (i < li.Count) piece = li[i];
                            sb2.Append(piece);
                            sb2.Append("｜");
                        }
                        if (i >= 1) sb2.Append("^");
                        sb.Append(sb2);
                    }
                }

                edit.EditorBase.Document.Replace(edit.EditorBase.SelectionStart, edit.EditorBase.SelectionLength, sb.ToString());
                edit.EditorBase.FormatTextTable();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("未能粘贴 Excel 数据块。错误信息如下：\r\n" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private LmeProjectJsonMessage JsonOfLmeProject { get; set; } = null;

        /// <summary>
        /// 自动从 码云（Gitee）网站获取关于此项目发行版的相关 Json 信息。
        /// </summary>
        private void TryToGetJsonOfLmeProject()
        {
            try
            {
                // 这段的用法改自：https://ask.csdn.net/questions/12093
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://gitee.com/api/v5/repos/LunarSF/Lunar-Markdown-Editor/releases/latest");
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                Stream ResStream = response.GetResponseStream();
                Encoding encoding = Encoding.GetEncoding("UTF-8");
                StreamReader streamReader = new StreamReader(ResStream, encoding);
                var jsonText = streamReader.ReadToEnd();

                JsonOfLmeProject = JsonConvert.DeserializeObject<LmeProjectJsonMessage>(jsonText);
                var curVersion = AssemblyInfoWrap.Version.Trim(new char[] { 'v', });
                var webVersion = JsonOfLmeProject.Tag_name.Trim(new char[] { 'v', });

                if (HasNewVersion(curVersion, webVersion))
                {
                    btnLastRelease.Visibility = Visibility.Visible;
                    btnLastRelease.ToolTip = "> 新版功能：" + JsonOfLmeProject.Name;
                }
                else
                {
                    btnLastRelease.Visibility = Visibility.Collapsed;
                    btnLastRelease.ToolTip = null;
                }
            }
            catch
            {
                JsonOfLmeProject = null;
                btnLastRelease.Visibility = Visibility.Collapsed;
                btnLastRelease.ToolTip = null;
            }
        }

        private bool HasNewVersion(string curVersion, string webVersion)
        {
            var cura = curVersion.Split('.');
            var weba = webVersion.Split('.');
            if (cura.Length != 4 || weba.Length != 4) return false;

            try
            {
                int cv = int.Parse(cura[0]); int wv = int.Parse(weba[0]);
                if (cv < wv) return true; else if (cv > wv) return false;
                // 如果相等，继续比较下一级版本号

                cv = int.Parse(cura[1]); wv = int.Parse(weba[1]);
                if (cv < wv) return true; else if (cv > wv) return false;
                // 如果相等，继续比较下一级版本号

                cv = int.Parse(cura[2]); wv = int.Parse(weba[2]);
                if (cv < wv) return true; else if (cv > wv) return false;
                // 如果相等，继续比较下一级版本号

                cv = int.Parse(cura[3]); wv = int.Parse(weba[3]);
                if (cv < wv) return true; else if (cv > wv) return false;
                // 如果相等，继续比较下一级版本号

                return false;  //四级版本号都相等。
            }
            catch { return false; }
        }

        /// <summary>
        /// 主窗口被关闭事件会检查此属性。如果存在安装包，会自动调用。
        /// </summary>
        private string SetupPackageFilePath { get; set; } = null;

        private void miLastRelease_Click(object sender, RoutedEventArgs e)
        {
            if (JsonOfLmeProject == null)
            {
                LMessageBox.Show("未能联网获取 Markdown To CHM(LME) 新发行版信息！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            System.Diagnostics.Process.Start("iexplore.exe", JsonOfLmeProject.Assets[0].Browser_download_url);
        }

        /// <summary>
        /// 启动时自动检查最新发行版本的 URL 路径。
        /// </summary>
        private string LastReleaseUrl { get; set; } = "";

        private void btnLastRelease_MouseEnter(object sender, MouseEventArgs e)
        {
            btnLastRelease.Background = Brushes.WhiteSmoke;
            btnLastRelease.Foreground = Brushes.Black;
        }

        private void btnLastRelease_MouseLeave(object sender, MouseEventArgs e)
        {
            btnLastRelease.Background = Brushes.YellowGreen;
            btnLastRelease.Foreground = Brushes.White;
        }

        #region 为什么要六个？！因为希望支持同时打开并编辑
        // 切换工作区时，必须关闭这六个 CSS 编辑器。
        private CssEditor lightLessonCssEditor = null;

        // 切换工作区时，必须关闭这六个 CSS 编辑器。
        private CssEditor darkLessonCssEditor = null;

        // 切换工作区时，必须关闭这六个 CSS 编辑器。
        private CssEditor lightMenuCssEditor = null;

        // 切换工作区时，必须关闭这六个 CSS 编辑器。
        private CssEditor darkMenuCssEditor = null;

        // 切换工作区时，必须关闭这六个 CSS 编辑器。
        private CssEditor lightPresentationCssEditor = null;

        // 切换工作区时，必须关闭这六个 CSS 编辑器。
        private CssEditor darkPresentationCssEditor = null;
        #endregion

        private void miEditCustomLightLessonCss_Click(object sender, RoutedEventArgs e)
        {
            if (lightLessonCssEditor != null)
            {
                lightLessonCssEditor.Close();
                if (lightLessonCssEditor.IsClosed)
                    lightLessonCssEditor = null;
                else return;  // 用户取消了“关闭”操作
            }

            lightLessonCssEditor = new CssEditor("lesson_light") { Owner = this, };
            lightLessonCssEditor.Show();
        }

        private void miEditCustomDarkLessonCss_Click(object sender, RoutedEventArgs e)
        {
            if (darkLessonCssEditor != null)
            {
                darkLessonCssEditor.Close();
                if (darkLessonCssEditor.IsClosed)
                    darkLessonCssEditor = null;
                else return;  // 用户取消了“关闭”操作
            }

            darkLessonCssEditor = new CssEditor("lesson_dark") { Owner = this, };
            darkLessonCssEditor.Show();
        }

        private void miEditCustomLightMenuCss_Click(object sender, RoutedEventArgs e)
        {
            if (lightMenuCssEditor != null)
            {
                lightMenuCssEditor.Close();
                if (lightMenuCssEditor.IsClosed)
                    lightMenuCssEditor = null;
                else return;  // 用户取消了“关闭”操作
            }

            lightMenuCssEditor = new CssEditor("menu_light") { Owner = this, };
            lightMenuCssEditor.Show();
        }

        private void miEditCustomDarkMenuCss_Click(object sender, RoutedEventArgs e)
        {
            if (darkMenuCssEditor != null)
            {
                darkMenuCssEditor.Close();
                if (darkMenuCssEditor.IsClosed)
                    darkMenuCssEditor = null;
                else return;  // 用户取消了“关闭”操作
            }

            darkMenuCssEditor = new CssEditor("menu_dark") { Owner = this, };
            darkMenuCssEditor.Show();
        }

        private void miEditCustomLightPresentationCss_Click(object sender, RoutedEventArgs e)
        {
            if (lightPresentationCssEditor != null)
            {
                lightPresentationCssEditor.Close();
                if (lightPresentationCssEditor.IsClosed)
                    lightPresentationCssEditor = null;
                else return;  // 用户取消了“关闭”操作
            }

            lightPresentationCssEditor = new CssEditor("presentation_light") { Owner = this, };
            lightPresentationCssEditor.Show();
        }

        private void miEditCustomDarkPresentationCss_Click(object sender, RoutedEventArgs e)
        {
            if (darkPresentationCssEditor != null)
            {
                darkPresentationCssEditor.Close();
                if (darkPresentationCssEditor.IsClosed)
                    darkPresentationCssEditor = null;
                else return;  // 用户取消了“关闭”操作
            }

            darkPresentationCssEditor = new CssEditor("presentation_dark") { Owner = this, };
            darkPresentationCssEditor.Show();
        }

        private void miTryToOpenHtmlWithWord_Click(object sender, RoutedEventArgs e)
        {
            var localPath = (previewFrame.Content as WebBrowser).Source.LocalPath;
            if (File.Exists(localPath) == false)
            {
                LMessageBox.Show("未找到当前正在预览的 Html 文件！此功能只能用于本地 Html 文件。", Globals.AppName,
                    MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (localPath.Contains(" "))
            {
                localPath = $"\"{localPath}\"";
            }

            string wordPath;
            if (SoftwareOperator.TryGetSoftwarePath(Softwares.WINWORD, out wordPath) == false)
            {
                LMessageBox.Show("在注册表中未找到 Microsoft Word 的安装路径！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (File.Exists(wordPath))
            {
                System.Diagnostics.Process.Start(wordPath, localPath);
            }
        }

        private void miTryToOpenHtmlWithDefaultBrowser_Click(object sender, RoutedEventArgs e)
        {
            var localPath = (previewFrame.Content as WebBrowser).Source.LocalPath;
            if (File.Exists(localPath) == false)
            {
                LMessageBox.Show("未找到当前正在预览的 Html 文件！此功能只能用于本地 Html 文件。", Globals.AppName,
                    MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            CustomMarkdownSupport.OpenLocalHtmlFileWithDefaultWebBrowser(localPath);
        }

        private void miTryToOpenHtmlWithWordPrompt_Click(object sender, RoutedEventArgs e)
        {
            LMessageBox.Show("这个功能是用在需要对网页进行少量排版并打印的场景。" +
                "由于打开的网页会被频繁地重新编译，所以如果需要保留 Word 编辑的结果，必须使用 Word 的【另存为...】" +
                "——否则下次预览或编辑作区时，用户在 Word 中的工作会全部丢失！！！",
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
        }

        private void miShowInExplorer_Click(object sender, RoutedEventArgs e)
        {
            var localPath = (previewFrame.Content as WebBrowser).Source.LocalPath;
            if (File.Exists(localPath) == false)
            {
                LMessageBox.Show("未找到当前正在预览的 Html 文件！此功能只能用于本地 Html 文件。", Globals.AppName,
                    MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                System.Diagnostics.Process.Start("explorer.exe", "/select, " +
                    (localPath.IndexOf(' ') >= 0 ? ("\"" + localPath + "\"") : localPath));
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void btnOpenPythonScriptDirectory_Click(object sender, RoutedEventArgs e)
        {
            var path = Globals.PathOfPythonScripts;
            if (path.Contains(" "))
            {
                path = "\"" + path + "\"";
            }

            System.Diagnostics.Process.Start("explorer.exe", path);
        }

        private bool askEnglishFileName = false;  // 默认开启后不适应。
        /// <summary>
        /// 在创建文件时，如果发现不是英文文件名，询问用户是否改用英文文件名。
        /// 此选项被关闭时，默认使用拼音首字母组成字符串作为文件名。
        /// </summary>
        public bool AskEnglishFileName
        {
            get { return askEnglishFileName; }
        }

        private bool showLmePyScripts = true;
        /// <summary>
        /// 启动时是否显示 LME 内置只读脚本。
        /// </summary>
        public bool ShowLmePyScripts
        {
            get { return showLmePyScripts; }
        }

        private string chmImageType = "";
        /// <summary>
        /// [只读]Chm 左边栏中使用何种图标。当为“Folder”时，可以改成“文件夹”图标和文档图标，
        /// 而不是“Book”图标和帮助文档图标。
        /// </summary>
        public string ChmImageType
        {
            get { return chmImageType; }
        }

        private List<string> ignoreAutoLinkTitles = new List<string>();
        /// <summary>
        /// 允许用户指定在自动创建到标题的链接时应忽略的标题——通常是重复性的标题。
        /// </summary>
        public List<string> IgnoreAutoLinkTitles
        {
            get { return ignoreAutoLinkTitles; }
        }

        private void ckxShowLmePyScripts_Click(object sender, RoutedEventArgs e)
        {
            if (ckxShowLmePyScripts.IsChecked == true)
            {
                showLmePyScripts = true;
            }
            else
            {
                showLmePyScripts = false;
            }

            App.ConfigManager.Set("ShowLmePyScripts", showLmePyScripts.ToString());
            LoadPythonScriptFiles();
        }

        /// <summary>
        /// 为什么不使用 tvWorkspaceDirectory 的 MouseLeave 事件？因为会导致 Popup 直接无法点击。
        /// 为什么不使用 tvWorkspaceDirectory 的 LostFocus 事件？因为根本起作用。
        /// </summary>
        private void mainTabControl_MouseEnter(object sender, MouseEventArgs e)
        {
            // 主窗口的 DeActived 事件也包括这个功能。
            if (tvWorkDirectory.SelectedItem == null) return;
            var selWtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selWtvi == null) return;

            selWtvi.PopToolBar.IsOpen = false;
        }

        private void miRemoveScripts_Click(object sender, RoutedEventArgs e)
        {
            miRemoveScripts.IsChecked = !miRemoveScripts.IsChecked;
            if (miRemoveScripts.IsChecked)
            {
                LMessageBox.Show("说明：\r\n　　⑴ 此功能会导致所有依赖于 JavaScript 的功能全部失效！请慎用！\r\n　　⑵ 下次启动时此功能会自动关闭！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "AppendTimeOfCompiling",
                HtmlOptionText = "临时禁止在网页中添加 JS 脚本",
            });
        }

        private void miOpenGitBash_Click(object sender, RoutedEventArgs e)
        {
            GitUtils.OpenGitBashOnCurrentWorkspace();
        }

        private void miOpenGitGui_Click(object sender, RoutedEventArgs e)
        {
            GitUtils.OpenGitGuiOnCurrentWorkspace();
        }

        private void miLocateSelectOutputDirectory_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.SelectedIndex < 0)
            {
                LMessageBox.Show("请先在“导出历史”列表中选择一个条目！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var roitem = lbxHistoryOutport.SelectedItem as RecentDirectoryListBoxItem;
            if (roitem == null) return;

            var localPath = roitem.DirectoryPath;

            try
            {
                System.Diagnostics.Process.Start("explorer.exe", "/select, " +
                    (localPath.IndexOf(' ') >= 0 ? ("\"" + localPath + "\"") : localPath));
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void BtnZipWorkspace_Click(object sender, RoutedEventArgs e)
        {
            var compressDialog = new CompressDialog() { Owner = this, WindowStartupLocation = WindowStartupLocation.CenterOwner, };
            compressDialog.Show();
        }

        private void MiAlwaysCompileBeforePreview_Click(object sender, RoutedEventArgs e)
        {
            miAlwaysCompileBeforePreview.IsChecked = !miAlwaysCompileBeforePreview.IsChecked;
            this.AlwaysCompileBeforePreview = miAlwaysCompileBeforePreview.IsChecked;
            App.ConfigManager.Set("AlwaysCompileBeforePreview", miAlwaysCompileBeforePreview.IsChecked.ToString());
        }

        private void MiCreateLinksForTitles_Click(object sender, RoutedEventArgs e)
        {
            this.CreateLinksForTitles =
                                    miCreateLinksForTitles.IsChecked = !miCreateLinksForTitles.IsChecked;
            App.WorkspaceConfigManager.Set("CreateLinksForTitles", miCreateLinksForTitles.IsChecked.ToString());

            OnHtmlOptionChanged(miAutoCollapseHtmlHeaders, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "CreateLinksForTitles",
                HtmlOptionText = "自动创建指向标题的链接",
            });
        }

        /// <summary>
        /// 批量创建子目录。
        /// </summary>
        private void MiNewEntriesFromTexttree_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("请先在工作区中选择一个目录作为基准目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var selDirectoryItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selDirectoryItem == null || selDirectoryItem.ItemType != WorkspaceTreeViewItem.Type.Folder)
            {
                LMessageBox.Show("请先在工作区中选择一个目录作为基准目录——其它条目不支持创建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var activeEditor = this.ActiveTextEditor;
            if (activeEditor == null)
            {
                LMessageBox.Show("请在当前活动编辑器中以树型文字表的形式创建目录短名——支持多层，但不支持特殊符号。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            try
            {
                DirectoryInfo baseDirectoryInfo = new DirectoryInfo(selDirectoryItem.FullPath);

                var fstLine = activeEditor.Document.GetLineByOffset(activeEditor.SelectionStart);
                var lstLine = activeEditor.Document.GetLineByOffset(activeEditor.SelectionStart + activeEditor.SelectionLength);

                List<TreeLine> levelAList = new List<TreeLine>();
                TreeLine preTreeLine = null;

                Regex invalidateReg = new Regex(@"^[-－][ 　\t]{1,}?");

                for (int i = fstLine.LineNumber; i <= lstLine.LineNumber; i++)
                {
                    var lineText = activeEditor.Document.GetText(activeEditor.Document.GetLineByNumber(i));
                    string header;
                    string tail;
                    int level;
                    if (MarkdownEditorBase.IsTreeListLine(lineText, out header, out tail, out level, true) == false)
                    {
                        LMessageBox.Show("选中的文本中有非树型列表行存在。",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    if (tail.EndsWith("~") || tail.IndexOf("~ ") >= 0 || tail.IndexOf("~\\ ") >= 0)
                    {
                        LMessageBox.Show("不允许创建以波形符(~)结尾的条目。",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }

                    var imatch = invalidateReg.Match(tail);
                    if (imatch.Success)
                    {
                        //LMessageBox.ShowWarning("减号(-)后面不能跟空白字符！\r\n\r\n　　" +
                        //    "说明：批量创建条目时，如果使用的树型文字表中条目最后一级以减号(-)开头，表示创建文件而非子目录——如果减号后面跟空白，标题会是空值。");
                        //return;
                        tail = "-" + tail.Substring(imatch.Length);
                    }

                    if (preTreeLine == null)
                    {
                        preTreeLine = new TreeLine() { Text = tail, Level = Math.Min(1, level), };
                        if (preTreeLine.EntryShortName == null)
                        {
                            LMessageBox.Show($"“{tail}”中有不适合用于创建目录的非法字符。",
                                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                        levelAList.Add(preTreeLine);
                    }
                    else
                    {
                        var newTreeLine = new TreeLine() { Text = tail, Level = level, };
                        if (newTreeLine.EntryShortName == null)
                        {
                            LMessageBox.Show($"“{tail}”中有不适合用于创建目录的非法字符。",
                                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }

                        if (preTreeLine.Level == newTreeLine.Level)
                        {
                            var parent = preTreeLine.Parent;
                            if (parent == null)
                            {
                                levelAList.Add(newTreeLine);
                            }
                            else
                            {
                                newTreeLine.Parent = parent;
                                parent.SubTreeLines.Add(newTreeLine);
                            }
                            preTreeLine = newTreeLine;
                        }
                        else if (newTreeLine.Level == preTreeLine.Level + 1)
                        {
                            newTreeLine.Parent = preTreeLine;
                            preTreeLine.SubTreeLines.Add(newTreeLine);
                            preTreeLine = newTreeLine;
                        }
                        else if (newTreeLine.Level < preTreeLine.Level)
                        {
                            var parent = GetParentTreeLine(preTreeLine, level);
                            if (parent == null)
                            {
                                levelAList.Add(newTreeLine);
                                preTreeLine = newTreeLine;
                            }
                            else
                            {
                                newTreeLine.Parent = parent;
                                parent.SubTreeLines.Add(newTreeLine);
                                preTreeLine = newTreeLine;
                            }
                        }
                        else
                        {
                            LMessageBox.Show("选中的树型文字表中有层级错误。",
                                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                            return;
                        }
                    }
                }

                if (levelAList.Count > 0)
                {
                    foreach (var tl in levelAList)
                    {
                        CreateEntries(tl, selDirectoryItem);
                    }

                    workspaceManager.SaveWorkspaceTreeviewToXml();
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// [递归]批量创建子目录。
        /// </summary>
        private WorkspaceTreeViewItem CreateEntries(TreeLine tl, WorkspaceTreeViewItem fi)
        {
            if (fi == null || fi.ItemType != WorkspaceTreeViewItem.Type.Folder) return null;

            var baseFullPath = fi.FullPath;
            if (string.IsNullOrWhiteSpace(baseFullPath)) return null;

            if (baseFullPath.EndsWith("\\") == false) baseFullPath += "\\";

            WorkspaceTreeViewItem newItem;
            if (tl.SubTreeLines.Count > 0 || (tl.EntryShortName.StartsWith("-") == false && tl.EntryShortName.StartsWith("－") == false))
            {
                var fullPath = baseFullPath + tl.EntryShortName + "\\";
                if (Directory.Exists(fullPath) == false)
                {
                    Directory.CreateDirectory(fullPath);
                    tl.CreatedFullPath = fullPath;
                }

                newItem = GetDirectoryItemByPath(fi, fullPath);
                if (newItem == null)
                {
                    newItem = new WorkspaceTreeViewItem(tl.Title, fullPath, "[-]", "", "", false, WorkspaceTreeViewItem.Type.Folder, 0, this);
                    fi.Items.Add(newItem);
                }

                if (File.Exists(newItem.MetaFilePath) == false)
                {
                    // 要自动创建目录元文件，否则无法保证 Title 有用！
                    CreateDirectoryMetaMdFile(new DirectoryInfo(newItem.FullPath), newItem.MetaFilePath, newItem.Title);
                }

                foreach (var subtl in tl.SubTreeLines)
                {
                    CreateEntries(subtl, newItem);
                }
            }
            else
            {
                // 如果是最后一层，且以减号开头，则创建文件而非目录。
                var fullPath = baseFullPath + tl.EntryShortName.TrimStart(new char[] { ' ', '　', '-', '－', }) + ".md";
                if (File.Exists(fullPath) == false)
                {
                    CreateMdFile(fullPath, tl.Title);
                    tl.CreatedFullPath = fullPath;
                }

                newItem = GetMdFileItemByPath(fi, fullPath);
                if (newItem == null)
                {
                    newItem = new WorkspaceTreeViewItem(tl.Title, fullPath, "[-]", "", tl.Title, false, WorkspaceTreeViewItem.Type.File, 0, this);
                    fi.Items.Add(newItem);
                }
            }

            return newItem;
        }

        private WorkspaceTreeViewItem GetDirectoryItemByPath(WorkspaceTreeViewItem parent, string newFullPath)
        {
            if (parent == null) return null;
            if (parent.Items.Count <= 0) return null;

            if (string.IsNullOrWhiteSpace(newFullPath)) return null;
            if (newFullPath.EndsWith("\\") == false) newFullPath += "\\";
            newFullPath = newFullPath.ToLower();

            foreach (var item in parent.Items)
            {
                var wi = item as WorkspaceTreeViewItem;
                if (wi == null) continue;

                var aPath = wi.FullPath;
                if (aPath.EndsWith("\\") == false) aPath += "\\";
                aPath = aPath.ToLower();

                if (aPath == newFullPath) return wi;
            }

            return null;
        }

        private WorkspaceTreeViewItem GetMdFileItemByPath(WorkspaceTreeViewItem parent, string newFullPath)
        {
            if (parent == null) return null;
            if (parent.Items.Count <= 0) return null;

            if (string.IsNullOrWhiteSpace(newFullPath)) return null;
            if (newFullPath.ToLower().EndsWith(".md") == false) newFullPath += ".md";
            newFullPath = newFullPath.ToLower();

            foreach (var item in parent.Items)
            {
                var wi = item as WorkspaceTreeViewItem;
                if (wi == null) continue;

                var aPath = wi.FullPath.ToLower();
                if (aPath.EndsWith(".md") == false) aPath += ".md";

                if (aPath == newFullPath) return wi;
            }

            return null;
        }

        private TreeLine GetParentTreeLine(TreeLine tl, int level)
        {
            if (tl == null) return null;

            var parent = tl.Parent;
            while (parent != null)
            {
                if (parent.Level == level - 1) return parent;

                parent = parent.Parent;
            }

            return null;
        }

        private void BtnSaveIgnoreAutoLinkTitles_Click(object sender, RoutedEventArgs e)
        {
            App.WorkspaceConfigManager.Set("IgnoreAutoLinkTitles", tbxIgnoreAutoLinkTitles.Text);
            ReadIgnoreAutoLinkTitles(tbxIgnoreAutoLinkTitles.Text);
        }

        private string ReadIgnoreAutoLinkTitles(string text = null)
        {
            ignoreAutoLinkTitles.Clear();
            if (text == null)
            {
                text = App.WorkspaceConfigManager.Get("IgnoreAutoLinkTitles");
                if (string.IsNullOrWhiteSpace(text)) return "";
            }

            var trimChars = new char[] { ' ', '　', '\t', };
            var lines = text.Split(new string[] { "\r\n", }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var line in lines)
            {
                var trimedLine = line.Trim(trimChars);
                if (trimedLine.StartsWith("%")) continue;

                if (ignoreAutoLinkTitles.Contains(trimedLine) == false)
                    ignoreAutoLinkTitles.Add(trimedLine);
            }

            return text;
        }

        private void BtnAddToIgnoreAutoLinkTitles_Click(object sender, RoutedEventArgs e)
        {
            var sb = new StringBuilder();
            foreach (var i in lbxTitles.Items)
            {
                var ci = i as CheckBoxItem;
                if (ci == null || ci.IsEnabled == false || ci.IsChecked == false) continue;
                sb.Append(ci.Title);
                sb.Append("\r\n");
            }

            if (tbxIgnoreAutoLinkTitles.Text.EndsWith("\r\n") == false)
            {
                tbxIgnoreAutoLinkTitles.AppendText("\r\n");
            }

            tbxIgnoreAutoLinkTitles.AppendText(sb.ToString());
        }

        private void HlListRepeatedTitles_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            lbxTitles.Items.Clear();

            var noneRepeatedTitleList = new List<string>();
            var repeatedTitlesList = new List<string>();

            if (tvWorkDirectory.Items.Count > 0)
            {
                RefreshAllTitles(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, ref noneRepeatedTitleList, ref repeatedTitlesList);
            }

            if (repeatedTitlesList.Count > 0)
            {
                lbxTitles.Items.Add(new ListBoxItem()
                {
                    Content = "※有重复标题：",
                    FontSize = 14,
                    Background = Brushes.SaddleBrown,
                    Foreground = Brushes.White,
                    Margin = new Thickness(4, 2, 2, 4),
                });

                repeatedTitlesList.Sort();

                foreach (var s in repeatedTitlesList)
                {
                    lbxTitles.Items.Add(new CheckBoxItem()
                    {
                        Title = s,
                        IsChecked = true,
                        Style = FindResource("MetroListBoxItem") as Style,
                    });
                }
            }

            if (noneRepeatedTitleList.Count > 0)
            {
                lbxTitles.Items.Add(new ListBoxItem()
                {
                    Content = "※无重复标题：",
                    FontSize = 14,
                    Background = Brushes.SaddleBrown,
                    Foreground = Brushes.White,
                    Margin = new Thickness(4, 2, 2, 4),
                });

                noneRepeatedTitleList.Sort();

                foreach (var s in noneRepeatedTitleList)
                {
                    lbxTitles.Items.Add(new CheckBoxItem()
                    {
                        Title = s,
                        IsChecked = false,
                        Style = FindResource("MetroListBoxItem") as Style,
                    });
                }
            }
        }

        private void RefreshAllTitles(WorkspaceTreeViewItem wtvi, ref List<string> noneRepeatedTitlesList, ref List<string> repeatedTitlesList)
        {
            if (wtvi == null) return;
            if (noneRepeatedTitlesList == null) noneRepeatedTitlesList = new List<string>();
            if (repeatedTitlesList == null) repeatedTitlesList = new List<string>();

            var trimChars = new char[] { ' ', '　', '\t', };
            var trimedTitle = wtvi.Title.Trim(trimChars);  // 这里还是分大小写好
            if (noneRepeatedTitlesList.Contains(trimedTitle) == false)
                noneRepeatedTitlesList.Add(trimedTitle);
            else
            {
                if (repeatedTitlesList.Contains(trimedTitle) == false && trimedTitle.EndsWith("~") == false)  // 资源目录本就应直接忽略。
                    repeatedTitlesList.Add(trimedTitle);
            }

            if (wtvi.Items.Count > 0)  // 有下级
            {
                foreach (var subItem in wtvi.Items)
                {
                    RefreshAllTitles(subItem as WorkspaceTreeViewItem, ref noneRepeatedTitlesList, ref repeatedTitlesList);
                }
            }
        }

        /// <summary>
        /// 重设默认图标组后，刷新对应条目图标。
        /// </summary>
        /// <param name="wtvi"></param>
        private void RefreshFoldersAndFilesIocns(WorkspaceTreeViewItem wtvi)
        {
            if (wtvi == null) return;
            if (wtvi.IsDirectoryExists)
            {
                wtvi.RefreshIcon();
                foreach (var subItem in wtvi.Items)
                {
                    RefreshFoldersAndFilesIocns(subItem as WorkspaceTreeViewItem);
                }
            }
            else if (wtvi.IsFileExists)
            {
                wtvi.RefreshIcon();
            }
        }

        private void MiChmImageType_Click(object sender, RoutedEventArgs e)
        {
            var mi = sender as MenuItem;
            if (mi == null) return;

            var tag = mi.Tag.ToString();
            if (tag == null) return;

            App.WorkspaceConfigManager.Set("ChmImageType", tag);
            chmImageType = tag;

            foreach (var smii in miChmImageTypes.Items)
            {
                var smi = smii as MenuItem;
                if (smi.Tag.ToString().ToLower() == chmImageType.ToLower())
                {
                    smi.IsChecked = true;
                }
                else smi.IsChecked = false;
            }

            if (tvWorkDirectory.Items.Count > 0)
                RefreshFoldersAndFilesIocns(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem);
        }

        private void BtnSetChmIcon_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem;
            if (selItem == null)
            {
                LMessageBox.Show("请先在工作区管理器中选中一个【普通目录】或【Markdown 文件条目】。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var selWtvi = selItem as WorkspaceTreeViewItem;
            if (selWtvi == null)
            {
                LMessageBox.Show("发生意外，当前选中的条目不是有效目录或文件条目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selWtvi.IsDirectoryExists)
            {
                chmFolderIconsPopup.PlacementTarget = btnSetChmIcon;
                chmFolderIconsPopup.Placement = PlacementMode.Right;
                (chmFolderIconsPopup.Child as ChmFolderIconsPopup).SelectImage(selWtvi.ChmImageIndex);
                chmFolderIconsPopup.IsOpen = true;
            }
            else if (selWtvi.IsFileExists)
            {
                chmFileIconsPopup.PlacementTarget = btnSetChmIcon;
                chmFileIconsPopup.Placement = PlacementMode.Right;
                (chmFileIconsPopup.Child as ChmFileIconPopup).SelectImage(selWtvi.ChmImageIndex);
                chmFileIconsPopup.IsOpen = true;
            }
            else
            {
                LMessageBox.Show("此类型条目在 CHM 文件不支持指定图标。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
        }

        private Popup chmFolderIconsPopup = new Popup()
        {
            Child = new ChmFolderIconsPopup(),
            SnapsToDevicePixels = true,
        };

        private Popup chmFileIconsPopup = new Popup()
        {
            Child = new ChmFileIconPopup(),
            SnapsToDevicePixels = true,
        };

        private void BtnOpenWorkspaceInExplorer_Click(object sender, RoutedEventArgs e)
        {
            OpenWorkspaceWithExplorer();
        }

        private void BtnFindWorkspaceItemsOptions_Initialized(object sender, EventArgs e)
        {
            cmFindWorkspaceItemsOptions.Placement = PlacementMode.Absolute;
            cmFindWorkspaceItemsOptions.PlacementTarget = btnFindWorkspaceItemsOptions;

            btnFindWorkspaceItemsOptions.ContextMenu = null;

            cmFindWorkspaceItemsOptions.Opened += CmFindWorkspaceItemsOptions_Opened;
        }

        private void CmFindWorkspaceItemsOptions_Opened(object sender, RoutedEventArgs e)
        {
            var point = btnFindWorkspaceItemsOptions.PointToScreen(new Point(0, 0));

            if (Environment.OSVersion.Version.Major > 6 ||
                (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))  // win 8 以上（6.2）
            {
                cmFindWorkspaceItemsOptions.HorizontalOffset = point.X - cmFindWorkspaceItemsOptions.ActualWidth + btnFindWorkspaceItemsOptions.ActualWidth + 6;
                cmFindWorkspaceItemsOptions.VerticalOffset = point.Y - cmFindWorkspaceItemsOptions.ActualHeight + 6;
            }
            else
            {
                cmFindWorkspaceItemsOptions.HorizontalOffset = point.X - cmFindWorkspaceItemsOptions.ActualWidth + btnFindWorkspaceItemsOptions.ActualWidth;
                cmFindWorkspaceItemsOptions.VerticalOffset = point.Y - cmFindWorkspaceItemsOptions.ActualHeight;
            }
        }

        private void MiFindWorkspaceItemWithRegex_Click(object sender, RoutedEventArgs e)
        {
            if (miFindWorkspaceItemWithRegex.IsChecked == true)
            {
                miFindWorkspaceItemWithRegex.IsChecked = false;
            }
            else
            {
                miFindWorkspaceItemWithRegex.IsChecked = true;
                miFindWorkspaceItemWithFirstLetter.IsChecked = false;  // 互斥
            }
        }

        private void BtnFindWorkspaceItemsOptions_Click(object sender, RoutedEventArgs e)
        {
            cmFindWorkspaceItemsOptions.IsOpen = true;
        }

        private void MiSearchDirectoryOrFileName_Click(object sender, RoutedEventArgs e)
        {
            if (miSearchDirectoryOrFileName.IsChecked == true)
            {
                miSearchDirectoryOrFileName.IsChecked = false;
            }
            else
            {
                miSearchDirectoryOrFileName.IsChecked = true;
            }
        }

        private void MiSearchTitle_Click(object sender, RoutedEventArgs e)
        {
            if (miSearchTitle.IsChecked == true)
            {
                miSearchTitle.IsChecked = false;
            }
            else
            {
                miSearchTitle.IsChecked = true;
            }
        }

        private void MiCopySearcedFiles_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (searchedWorkspaceItemList == null || searchedWorkspaceItemList.Count <= 0)
                {
                    LMessageBox.Show("没有可复制的搜索结果。请尝试再次执行搜索操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var files = new System.Collections.Specialized.StringCollection();
                foreach (var item in searchedWorkspaceItemList)
                {
                    switch (item.ItemType)
                    {
                        case WorkspaceTreeViewItem.Type.File:
                        case WorkspaceTreeViewItem.Type.Image:
                        case WorkspaceTreeViewItem.Type.Sound:
                        case WorkspaceTreeViewItem.Type.Vedio:
                            {
                                files.Add(item.FullPath);
                                break;
                            }
                        case WorkspaceTreeViewItem.Type.Folder:
                            {
                                files.Add(item.MetaFilePath);
                                break;
                            }
                            // 其它类型不允许复制。
                    }
                }
                Clipboard.SetFileDropList(files);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void MiFindWorkspaceItemWithFirstLetter_Click(object sender, RoutedEventArgs e)
        {
            if (miFindWorkspaceItemWithFirstLetter.IsChecked == true)
            {
                miFindWorkspaceItemWithFirstLetter.IsChecked = false;
            }
            else
            {
                miFindWorkspaceItemWithFirstLetter.IsChecked = true;
                miFindWorkspaceItemWithRegex.IsChecked = false;  // 互斥
            }
        }

        private void BtnPresentate_Click(object sender, RoutedEventArgs e)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                if (ActivedEditor == null)
                {
                    LMessageBox.Show("请先在工作区管理器中选中一个要演示的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
                else
                {
                    CompileAndPreviewHtml();
                    return;
                }
            }

            switch (wtvi.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        wtvi.OpenFile();  // 不会造成重复打开的，不用担心。
                        CompileAndPreviewHtml();
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Image:
                    {
                        ImagePreviewWindow ipw = new ImagePreviewWindow(wtvi.FullPath, wtvi.Title)
                        {
                            Owner = App.Current.MainWindow,
                            WindowStartupLocation = WindowStartupLocation.CenterOwner,
                            WindowState = WindowState.Maximized,
                            Height = 300,
                            Width = 400,
                        };
                        ipw.Show();  //允许多张图片同时演示
                        break;
                    }
                default:
                    {
                        LMessageBox.Show("暂时只支持 Markdown 文档和图像文件的预览！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
            }
        }

        private void MiIE8_Click(object sender, RoutedEventArgs e)
        {
            SelectIE_X_UA_Compatible(sender);
        }

        private void MiIE9_Click(object sender, RoutedEventArgs e)
        {
            SelectIE_X_UA_Compatible(sender);
        }

        private void MiIE10_Click(object sender, RoutedEventArgs e)
        {
            SelectIE_X_UA_Compatible(sender);
        }

        private void MiIE11_Click(object sender, RoutedEventArgs e)
        {
            SelectIE_X_UA_Compatible(sender);
        }

        private void MiIEDefault_Click(object sender, RoutedEventArgs e)
        {
            SelectIE_X_UA_Compatible(sender);
        }

        private void SelectIE_X_UA_Compatible(object sender)
        {
            var mi = sender as MenuItem;
            if (mi == null || mi.Tag == null)
            {
                LMessageBox.ShowWarning("未传入 sender 对象，不能执行。");
                return;
            }
            var ieVersion = mi.Tag as string;
            if (string.IsNullOrWhiteSpace(ieVersion))
            {
                LMessageBox.ShowWarning("未传入 ieVersion 值，不能执行。");
                return;
            }

            RefreshIE_X_UA_Compatible(ieVersion);

            App.WorkspaceConfigManager.Set("IE_X_UA_Compatible", ieVersion);
            OnHtmlOptionChanged(sender, new HtmlCompileChangedEventArgs()
            {
                HtmlOptionID = "IE_X_UA_Compatible",
                HtmlOptionText = ieVersion,
            });
        }

        /// <summary>
        /// 根据“ieVersion”的值来刷新主界面上关于编译 Html 时要指定的 IE 兼容版本的几个菜单的选取状态。
        /// </summary>
        /// <param name="ieVersion"></param>
        private void RefreshIE_X_UA_Compatible(string ieVersion)
        {
            if (string.IsNullOrEmpty(ieVersion))
            {
                ieVersion = "Edge";  // 默认随系统。
            }

            if (ieVersion == "8")
            {
                defaultIeVersion = "8";
                miIE8.IsChecked = true;

                miIE9.IsChecked = false;
                miIE10.IsChecked = false;
                miIE11.IsChecked = false;
                miIEDefault.IsChecked = false;
            }
            else if (ieVersion == "9")
            {
                defaultIeVersion = "9";
                miIE8.IsChecked = false;
                miIE9.IsChecked = true;

                miIE10.IsChecked = false;
                miIE11.IsChecked = false;
                miIEDefault.IsChecked = false;
            }
            else if (ieVersion == "9")
            {
                defaultIeVersion = "9";
                miIE8.IsChecked = false;
                miIE9.IsChecked = false;
                miIE10.IsChecked = true;

                miIE11.IsChecked = false;
                miIEDefault.IsChecked = false;
            }
            else if (ieVersion == "10")
            {
                defaultIeVersion = "10";
                miIE8.IsChecked = false;
                miIE9.IsChecked = false;
                miIE10.IsChecked = false;
                miIE11.IsChecked = true;

                miIEDefault.IsChecked = false;
            }
            else if (ieVersion == "11")
            {
                defaultIeVersion = "11";
                miIE8.IsChecked = false;
                miIE9.IsChecked = false;
                miIE10.IsChecked = false;
                miIE11.IsChecked = true;

                miIEDefault.IsChecked = false;
            }
            else//都强制为UTF-8
            {
                if (ieVersion != "Edge")
                {
                    ieVersion = "Edge";
                }

                defaultIeVersion = "Edge";
                miIE8.IsChecked = false;
                miIE9.IsChecked = false;
                miIE10.IsChecked = false;
                miIE11.IsChecked = false;
                miIEDefault.IsChecked = true;
            }
        }

        private void MiChmNavBarShow_Click(object sender, RoutedEventArgs e)
        {
            SetChmNavBarVisible(sender);
        }

        private void MiChmNavBarHide_Click(object sender, RoutedEventArgs e)
        {
            SetChmNavBarVisible(sender);
        }

        private string chmNavBarVisible = "True";
        /// <summary>
        /// 编译的 CHM 默认情况下是否显示左侧导航面板。
        /// </summary>
        public string ChmNavBarVisible
        {
            get
            {
                return chmNavBarVisible;
            }
        }

        private void SetChmNavBarVisible(object sender)
        {
            var mi = sender as MenuItem;
            if (mi == null || mi.Tag == null)
            {
                LMessageBox.ShowWarning("未传入 sender 对象，不能执行。");
                return;
            }
            var visible = mi.Tag as string;
            if (string.IsNullOrWhiteSpace(visible))
            {
                LMessageBox.ShowWarning("未传入 ieVersion 值，不能执行。");
                return;
            }

            chmNavBarVisible = visible;
            RefreshChmNavBarVisible(visible);

            App.WorkspaceConfigManager.Set("ChmNavBarVisible", visible);
        }

        /// <summary>
        /// 根据“ieVersion”的值来刷新主界面上关于编译 Html 时要指定的 IE 兼容版本的几个菜单的选取状态。
        /// </summary>
        /// <param name="visible"></param>
        private void RefreshChmNavBarVisible(string visible)
        {
            if (string.IsNullOrEmpty(visible))
            {
                visible = "True";  // 默认显示。
            }

            chmNavBarVisible = visible;

            if (visible == "True")
            {
                miChmNavBarShow.IsChecked = true;
                miChmNavBarHide.IsChecked = false;
            }
            else
            {
                miChmNavBarShow.IsChecked = false;
                miChmNavBarHide.IsChecked = true;
            }
        }

        private string chmImportDirectoryPathes = "";
        /// <summary>
        /// CHM 引入的外部文件的路径列表（以分号分隔）。
        /// 注意：这些文件必须都放在当前工作区根目录下的名为“_xxx~”的文件夹中。
        /// </summary>
        public string ChmImportDirectoryPathes
        {
            get { return chmImportDirectoryPathes; }
        }

        private void MiChmInportFiles_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var di = new DirectoryInfo(Globals.PathOfWorkspace);
                if (di.Exists == false)
                {
                    LMessageBox.ShowWarning("当前工作区目录不存在！");
                    return;
                }

                var subDis = di.GetDirectories("_*~", SearchOption.TopDirectoryOnly);
                if (subDis.Length <= 0)
                {
                    LMessageBox.ShowWarning("当前工作区目录下没有可用的子目录！\r\n\r\n注意：所有引用的外部文件都需要放在工作区目录下的某个形如【_xxx~】的子目录下——即子目录名必须以 _ 开头并以 ~ 结尾。");
                    return;
                }

                var cifdlg = new ChmInportFilesDialog(subDis, chmImportDirectoryPathes) { Owner = this, };
                if (cifdlg.ShowDialog() == true)
                {
                    chmImportDirectoryPathes = cifdlg.ImportedDirectories;
                    App.WorkspaceConfigManager.Set("ChmInportedFiles", ChmImportDirectoryPathes);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        private void BtnCopyColorHexadecimaString_Click(object sender, RoutedEventArgs e)
        {
            var hexText = btnCopyColorHexadecimaString.Tag as string;
            if (string.IsNullOrWhiteSpace(hexText))
            {
                LMessageBox.ShowWarning("请用鼠标左键点选一个色彩。");
                return;
            }
            Clipboard.SetText(hexText);
        }

        private void BtnInsertColorHexadecimaString_Click(object sender, RoutedEventArgs e)
        {
            var hexText = btnInsertColorHexadecimaString.Tag as string;
            if (string.IsNullOrWhiteSpace(hexText))
            {
                LMessageBox.ShowWarning("请用鼠标左键点选一个色彩。");
                return;
            }
            var edit = ActiveTextEditor;
            if (edit != null)
            {
                edit.SelectedText = hexText;
                edit.Select(edit.SelectionStart + hexText.Length, 0);
            }
        }

        private void MiAskEnglishFileName_Click(object sender, RoutedEventArgs e)
        {
            askEnglishFileName = !askEnglishFileName;
            miAskEnglishFileName.IsChecked = askEnglishFileName;
            App.ConfigManager.Set("AskEnglishFileName", askEnglishFileName.ToString());
        }

        private void MiWriteControlStyle_Click(object sender, RoutedEventArgs e)
        {
            string templateText = GetStyleXamlCode(btnSearchInWorkspace as Control);
            File.WriteAllText("D:\\控件样式.txt", templateText);
            LMessageBox.Show("控件样式文件已输出到：D:\\控件样式.txt");
        }

        private void CmiLinkThisImage_Click(object sender, RoutedEventArgs e)
        {
            var wti = imagePreviewOutBorder.Tag as WorkspaceTreeViewItem;
            if (wti == null || wti.ItemType != WorkspaceTreeViewItem.Type.Image)
            {
                LMessageBox.Show("未找到对应图像文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var imageFilePath = wti.FullPath;

            //在当前文档中添加对当前图片的引用。
            if (File.Exists(imageFilePath) == false)
            {
                LMessageBox.Show("未找到对应图像文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果该图片不在当前文件资源文件夹下，先复制，然后再添加。
            var selEditor = Globals.MainWindow.ActivedEditor;
            if (selEditor == null) return;
            if (File.Exists(selEditor.FullFilePath) == false)
            {
                LMessageBox.Show("需要先保存正在编辑的文件才能使用这个功能。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //这里要注意：
            //在工作区内的直接添加引用，不在工作区的才复制文件。
            selEditor.TryToDropResourcesFiles(new System.Collections.Specialized.StringCollection() { imageFilePath, }, false);
        }

        private void MiValidateImageLinksInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            Globals.ValidateImageLinks();
        }

        private void MiValidateImageLinksInWorkspace_Click(object sender, RoutedEventArgs e)
        {
            var editors = Editors;
            var hasModified = false;
            foreach (var editor in editors)
            {
                if (editor.IsModified)
                {
                    hasModified = true;
                    break;
                }
            }

            if (hasModified)
            {
                LMessageBox.Show("在工作区中进行校验会直接读取磁盘文件内容。\r\n\r\n　　请先保存所有文件！\r\n\r\n　　如果不希望保存，可以单独针对当前打开的文档进行校验！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            Globals.ValidateImageLinksInWorkspace();
        }

        private void MiHtmlFactory_Click(object sender, RoutedEventArgs e)
        {
            var lWin = new HtmlFactory() { Owner = this, Height = 500, Width = 800 };
            lWin.ShowDialog();
        }

        private void MiPrintPreviewHtml_Click(object sender, RoutedEventArgs e)
        {
            var web = previewFrame.Content as WebBrowser;
            mshtml.IHTMLDocument2 doc = web.Document as mshtml.IHTMLDocument2;
            doc.execCommand("Print", true, null);
        }

        private void gsResourcePreview_MouseEnter(object sender, MouseEventArgs e)
        {
            // 解决左边栏中间的 GridSplitter 向上拖动会越界的问题。
            rdResourcePreviewArea.MaxHeight = leftToolBarGrid.ActualHeight - 10;
        }

        private void MetroWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            // 解决左边栏中间的 GridSplitter 向上拖动会越界的问题。
            rdResourcePreviewArea.MaxHeight = leftToolBarGrid.ActualHeight - 10;
        }

        private void gsResourcePreview_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            if (rdLeftToolsTop.ActualHeight <= rdResourcePreviewArea.ActualHeight)
            {
                rdLeftToolsTop.Height = new GridLength(rdLeftToolsTop.ActualHeight, GridUnitType.Pixel);
                rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
            }
            else if (rdLeftToolsTop.ActualHeight == rdResourcePreviewArea.ActualHeight)
            {
                rdLeftToolsTop.Height =
                    rdResourcePreviewArea.Height = new GridLength(1, GridUnitType.Star);
            }
            else
            {
                rdLeftToolsTop.Height = new GridLength(1, GridUnitType.Star);
                rdResourcePreviewArea.Height = new GridLength(rdResourcePreviewArea.ActualHeight, GridUnitType.Pixel);
            }
        }

        private void miSetWorkspace_Click(object sender, RoutedEventArgs e)
        {
            ChangeWorkspace();
        }

        private void miAppendHeaderLine_Click(object sender, RoutedEventArgs e)
        {
            this.AppendHeadLine =
                miAppendHeadLine.IsChecked = !miAppendHeadLine.IsChecked;
            App.WorkspaceConfigManager.Set("AppendHeadLine", this.AppendHeadLine.ToString());
        }

        private void miAppendFootLine_Click(object sender, RoutedEventArgs e)
        {
            this.AppendFootLine =
                   miAppendFootLine.IsChecked = !miAppendFootLine.IsChecked;
            App.WorkspaceConfigManager.Set("AppendFootLine", this.AppendFootLine.ToString());
        }

        private void miAppendThemeSwitcher_Click(object sender, RoutedEventArgs e)
        {
            this.AppendThemeSwitcher =
                miAppendThemeSwitcher.IsChecked = !miAppendThemeSwitcher.IsChecked;
            App.WorkspaceConfigManager.Set("AppendThemeSwitcher", this.AppendThemeSwitcher.ToString());
        }

        private void miCopyStructAndCreateNewWorkspace_Click(object sender, RoutedEventArgs e)
        {
            CopyStructAndCreateNewWorkspace();
        }

        /// <summary>
        /// 解决 Win 10 下 PopUp 位置偏移的问题。
        /// </summary>
        private static void FixPopupBug()
        {
            var ifLeft = SystemParameters.MenuDropAlignment;
            if (ifLeft)
            {
                var t = typeof(SystemParameters);
                var field = t.GetField("_menuDropAlignment", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
                field?.SetValue(null, false);
            }
        }

        private void Menu_Initialized(object sender, EventArgs e)
        {
            var m = sender as MenuItem;
            if (m == null) return;
            var cm = m.ContextMenu;
            if (cm == null) return;

            cm.Placement = PlacementMode.Bottom;
            cm.PlacementTarget = m;
            m.ContextMenu = null;
            m.Tag = cm;

            cm.Opened += Cm_Opened;
            cm.Closed += Cm_Closed;
            m.PreviewMouseLeftButtonDown += M_PreviewMouseLeftButtonDown;
        }

        private void M_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var m = sender as MenuItem;
            if (m == null) return;
            var cm = m.Tag as ContextMenu;
            if (cm == null) return;

            cm.IsOpen = true;
        }

        private void Cm_Closed(object sender, RoutedEventArgs e)
        {
            var cm = sender as ContextMenu;
            if (cm == null || cm.PlacementTarget == null) return;
            var m = cm.PlacementTarget as MenuItem;
            if (m == null) return;

            m.Background = Brushes.Transparent;
            m.Foreground = Brushes.White;
        }

        private void Cm_Opened(object sender, RoutedEventArgs e)
        {
            var cm = sender as ContextMenu;
            if (cm == null || cm.PlacementTarget == null) return;
            var m = cm.PlacementTarget as MenuItem;
            if (m == null) return;

            m.Background = Brushes.LightGray;
            m.Foreground = Brushes.Black;

            var point = cm.PlacementTarget.PointToScreen(new Point(0, 0));
            //if (Environment.OSVersion.Version.Major > 6 ||
            //    (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))  // win 8 以上（6.2）
            //{
            cm.HorizontalOffset = 0;  // -cm.ActualWidth + m.ActualWidth + 6;// point.X - splitMenu2.ActualWidth + btnFindHeaders_AppendArrow.ActualWidth;
            cm.VerticalOffset = 0;  // point.Y - 8;
            //}
        }

        private void miSetWrapText_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var wts = new WrapTextSetter() { Owner = this, };

                if (string.IsNullOrEmpty(leftWrapText) == false)
                    wts.tbxFst.Text = leftWrapText;
                else wts.tbxFst.Text = "";

                if (string.IsNullOrEmpty(RightWrapText) == false)
                    wts.tbxSec.Text = rightWrapText;
                else wts.tbxSec.Text = "";

                if (wts.ShowDialog() != true) return;

                var lwt = wts.tbxFst.Text;
                leftWrapText = lwt;
                App.ConfigManager.Set("LeftWrapText", lwt);

                var rwt = wts.tbxSec.Text;
                rightWrapText = rwt;
                App.ConfigManager.Set("RightWrapText", rwt);

                //var lwt = InputBox.Show(Globals.AppName, "请输入左封装字符串（例如 【）：", LeftWrapText, false, "在这里设置用于按下 ` 键时在选定文本左侧插入的字符串。");
                //if (lwt != null)
                //{
                //    leftWrapText = lwt;
                //    App.ConfigManager.Set("LeftWrapText", lwt);

                //    var rwt = InputBox.Show(Globals.AppName, "请输入右封装字符串（例如 】）：", RightWrapText, false, "在这里设置用于按下 ` 键时在选定文本右侧插入的字符串。");
                //    if (rwt != null)
                //    {
                //        rightWrapText = rwt;
                //        App.ConfigManager.Set("RightWrapText", rwt);
                //    }
                //}
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        private void Menu_Click(object sender, RoutedEventArgs e)
        {
            var menu = sender as MenuItem;
            if (menu == null || menu.Tag == null) return;
            var cm = menu.Tag as ContextMenu;
            if (cm == null) return;

            cm.IsOpen = true;
        }

        /// <summary>
        /// 检索出当前工作区中所有未被引用的图像文件，允许用户直接删除之。
        /// </summary>
        private void miFindUnLinkedImages_Click(object sender, RoutedEventArgs e)
        {
            FindUnLinkedImages();
        }

        public void FindUnLinkedImages()
        {
            var count = 0;
            foreach (var ue in mainTabControl.Items)
            {
                var editor = ue as MarkdownEditor;
                if (editor != null && editor.IsModified)
                {
                    count++;
                    break;
                }
            }

            if (count > 0)
            {
                var answer = LMessageBox.Show("此操作须保存当前编辑的所有文件。要继续吗？", Globals.AppName,
                    MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (answer != MessageBoxResult.Yes) return;
            }

            LMessageBox.ShowWarning("请注意：\r\n　　1. 此操作须扫描当前工作区中所有文件，耗时较长，请耐心等待。\r\n" +
                "　　2. 此操作不能识别 Html 格式的图像链接，存在【误认为某图像未被引用】的可能性！！\r\n" +
                "　　3. 此操作不能识别图像链接【是否在注释中】或者【是否在代码块】中。故可能有漏网之鱼！！\r\n\r\n" +
                "　　平时养成良好的使用习惯才能使此功能精确识别未被引用的图像文件！！");

            try
            {
                // 第1步，递归列出当前工作区中所有图像文件列表集（集中每个列表中一个“Images~”目录下的图像文件路径列表）
                //        同时，列出当前 Images~目录对应的 Markdown 文件的路径。
                List<ImageDirectoryList<string>> imageInfosList = new List<ImageDirectoryList<string>>();
                List<string> mdFilePathes = new List<string>();
                List<ImageTitleAndPath> titleInfos = new List<ImageTitleAndPath>();

                var imageFileSuffixReg = new Regex(@".*?\.(png|jpeg|jpg|bmp|gif)$");
                ReadImageInfos(new DirectoryInfo(Globals.PathOfWorkspace), ref imageFileSuffixReg, ref imageInfosList, ref mdFilePathes, ref titleInfos);

                //StringBuilder sb = new StringBuilder();
                //foreach (var idil in imageInfosList)
                //{
                //    sb.Append("MD File:" + idil.MasterMarkdownFileInfo + "\r\n");
                //    foreach (var ii in idil)
                //    {
                //        sb.Append("　　" + ii.FullName + "\r\n");
                //    }
                //}

                //LMessageBox.ShowInfomation(sb.ToString());
                List<string> linkedImageRealPaths = new List<string>();

                foreach (var idil in imageInfosList)
                {
                    using (StreamReader sr = new StreamReader(idil.MasterMarkdownFilePath))
                    {
                        var mdText = sr.ReadToEnd();
                        var imgLinks = Utils.ImageLinkTool.GetImageLinksFromText(mdText);

                        for (int i = mdFilePathes.Count - 1; i >= 0; i--)
                        {
                            if (mdFilePathes[i].ToLower() == idil.MasterMarkdownFilePath.ToLower())
                            {
                                mdFilePathes.RemoveAt(i);
                            }
                        }

                        if (imgLinks != null && imgLinks.Count > 0)
                        {
                            var imgLinkedPaths = new List<string>();
                            foreach (var imgLink in imgLinks)
                            {
                                var realPath = Utils.ImageLinkTool.GetRealPathFromImagelink(imgLink, idil.MasterMarkdownFilePath);
                                if (string.IsNullOrWhiteSpace(realPath) == false)
                                {
                                    imgLinkedPaths.Add(realPath.ToLower());
                                }
                            }

                            if (imgLinkedPaths.Count > 0)
                            {
                                linkedImageRealPaths.AddRange(imgLinkedPaths);  // 准备进行第二次判断（非对应MD文件引用）
                                for (int i = idil.Count - 1; i >= 0; i--)
                                {
                                    if (imgLinkedPaths.Contains<string>(idil[i]))
                                    {
                                        idil.RemoveAt(i);
                                    }
                                }
                            }
                        }
                    }
                }

                // 剩余的没有检验过后 Markdown 文件。
                foreach (var mdFilePath in mdFilePathes)
                {
                    if (mdFilePath.EndsWith("_tmp~.md") == false && File.Exists(mdFilePath))
                    {
                        using (StreamReader sr = new StreamReader(mdFilePath))
                        {
                            var mdText = sr.ReadToEnd();
                            var imgLinks = Utils.ImageLinkTool.GetImageLinksFromText(mdText);
                            if (imgLinks != null && imgLinks.Count > 0)
                            {
                                foreach (var imgLink in imgLinks)
                                {
                                    var realPath = Utils.ImageLinkTool.GetRealPathFromImagelink(imgLink, mdFilePath);
                                    if (string.IsNullOrWhiteSpace(realPath) == false)
                                    {
                                        linkedImageRealPaths.Add(realPath.ToLower());
                                    }
                                }
                            }
                        }
                    }
                }

                // 第二次判断，非由所在 MD 文件引用的情况
                List<string> unLinkedImagePathes = new List<string>();
                foreach (var idil in imageInfosList)
                {
                    for (int i = idil.Count - 1; i >= 0; i--)
                    {
                        var imgPath = idil[i];
                        if (linkedImageRealPaths.Contains<string>(imgPath))
                        {
                            idil.RemoveAt(i);
                        }
                    }
                    if (idil.Count > 0)
                    {
                        unLinkedImagePathes.AddRange(idil);
                    }
                }

                //StringBuilder sb = new StringBuilder();
                //foreach(var path in unLinkedImagePathes)
                //{
                //    sb.Append(path);
                //    sb.Append("\r\n");
                //}

                //LMessageBox.ShowWarning("当前工作区中有如下图像文件未被链接：\r\n" + sb.ToString());

                if (unLinkedImagePathes.Count > 0)
                {
                    var unlinkedImagesDialog = new UnLinkedImagesDialog(unLinkedImagePathes, titleInfos)
                    {
                        Owner = this,
                        WindowStartupLocation = WindowStartupLocation.CenterOwner,
                    };
                    unlinkedImagesDialog.ShowDialog();
                }
                else
                {
                    LMessageBox.ShowInfomation("恭喜，未找到没有被引用的图像文件！");
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        private void ReadImageInfos(DirectoryInfo di, ref Regex imageFileSuffixReg,
            ref List<ImageDirectoryList<string>> imageInfosList, ref List<string> mdFilePathes,
            ref List<ImageTitleAndPath> titleInfos)
        {
            // 第1步，当前目录中Markdown 文件（含目录元文件）及其对应的资源目录。
            var mdFiles = di.GetFiles("*.md");
            foreach (var mdf in mdFiles)
            {
                mdFilePathes.Add(mdf.FullName.ToLower());
            }

            foreach (var mdFile in mdFiles)
            {
                var shortName = mdFile.Name;
                if (shortName.ToLower().EndsWith(".md"))
                    shortName = shortName.Substring(0, shortName.Length - 3);
                var imageResourceFolderPath = mdFile.Directory + "\\" + shortName + "~\\Images~\\";
                if (Directory.Exists(imageResourceFolderPath))
                {
                    ImageDirectoryList<string> imageInfos = new ImageDirectoryList<string>();
                    var files = new DirectoryInfo(imageResourceFolderPath).GetFiles();
                    foreach (var file in files)
                    {
                        if (imageFileSuffixReg.Match(file.Name).Success)
                            imageInfos.Add(file.FullName.ToLower());
                    }
                    if (imageInfos.Count > 0)
                    {
                        imageInfos.MasterMarkdownFilePath = mdFile.FullName.ToLower();
                        imageInfosList.Add(imageInfos);
                    }
                }
                var imgTitleFilePath = imageResourceFolderPath + "~.txt";
                if (File.Exists(imgTitleFilePath))
                {
                    using (StreamReader sr = new StreamReader(imgTitleFilePath))
                    {
                        var lines = sr.ReadToEnd().Split(new char[] { '\r', '\n', }, StringSplitOptions.RemoveEmptyEntries);
                        if (lines.Length > 0)
                        {
                            foreach (var line in lines)
                            {
                                var pieces = line.Split(new char[] { '|', }, StringSplitOptions.RemoveEmptyEntries);
                                if (pieces.Length == 2)
                                {
                                    titleInfos.Add(new ImageTitleAndPath()
                                    {
                                        Title = pieces[1],
                                        ImageFilePath = (imageResourceFolderPath + pieces[0]).ToLower(),
                                    });
                                }
                            }
                        }
                    }
                }
            }

            var subDirs = di.GetDirectories();
            foreach (var subdi in subDirs)
            {
                if (subdi.Name.EndsWith("~")) continue;
                // 第2步，递归读取当前目录下普通子目录中的图像文件信息。
                ReadImageInfos(subdi, ref imageFileSuffixReg, ref imageInfosList, ref mdFilePathes, ref titleInfos);
            }
        }

        private void miExportTestPaper_Click(object sender, RoutedEventArgs e)
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.ShowWarning("当前没有打开任何文档！");
                return;
            }

            var errorMsg = new StringBuilder();
            var questions = QuestionBuilder.BuildQuestions(ae, errorMsg);
            if (questions.Count <= 0)
            {
                LMessageBox.ShowWarning("文档中没有任何可以提取的试题！");
                return;
            }

            StringBuilder sbAll, sbAnswerLetters, sbQuestion, sbAnalysysAndAnswer;
            QuestionBuilder.ExportQuestionsFromText(ref questions, out sbAll, out sbAnswerLetters, out sbQuestion, out sbAnalysysAndAnswer);

            string answerLetters;
            if (sbAnswerLetters.Length > 0)
            {
                answerLetters = "\r\n\r\n=========参考答案=========\r\n" + sbAnswerLetters.ToString();
            }
            else
            {
                answerLetters = "";
            }

            Clipboard.SetData(DataFormats.Text,
                ae.Title + "\r\n" +
                answerLetters +
                "\r\n\r\n==========试　卷==========\r\n" +
                sbQuestion.ToString() +
                "\r\n\r\n==========教师版==========\r\n" +
                sbAll.ToString() +
                "\r\n\r\n==========纯解析==========\r\n" +
                sbAnalysysAndAnswer.ToString());
            LMessageBox.Show("此文档所有试题均已复制到剪贴板！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information, "", null, this);

        }

        private void miExportTestPaperToWord_Click(object sender, RoutedEventArgs e)
        {
            // 放到另一个类中是为了减轻这个文件的复杂度。
            QuestionBuilder.ExportToWordDocument(ActivedEditor);
        }

        /// <summary>
        /// 按赛事星网站要求的模板格式提取试题文本。
        /// </summary>
        private void miExportTestPaperModel_Click(object sender, RoutedEventArgs e)
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.ShowWarning("当前没有打开任何文档！");
                return;
            }

            var errorMsg = new StringBuilder();
            var questions = QuestionBuilder.BuildQuestions(ae, errorMsg);
            if (questions.Count <= 0)
            {
                LMessageBox.ShowWarning("文档中没有任何可以提取的试题！");
                return;
            }

            StringBuilder sbAll;
            QuestionBuilder.ExportQuestionsForSaiShiXing(ref questions, out sbAll);

            Clipboard.SetData(DataFormats.Text, sbAll.ToString());
            LMessageBox.Show("请将剪贴板中数据粘贴到赛事星模板中！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information, "", null, this);

        }

        /// <summary>
        /// 按问卷星网站要求的模板格式提取试题文本。
        /// </summary>
        private void miExportTestPaperWenJuanXing_Click(object sender, RoutedEventArgs e)
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.ShowWarning("当前没有打开任何文档！");
                return;
            }

            var errorMsg = new StringBuilder();
            var questions = QuestionBuilder.BuildQuestions(ae, errorMsg);
            if (questions.Count <= 0)
            {
                LMessageBox.ShowWarning("文档中没有任何可以提取的试题！");
                return;
            }

            StringBuilder sbAll;
            QuestionBuilder.ExportQuestionsForWenJuanXing(ref questions, out sbAll);
            var header = @"请输入您的姓名：

请输入您的班级：

";
            Clipboard.SetData(DataFormats.Text, header + sbAll.ToString());
            LMessageBox.Show("请将剪贴板中数据粘贴到问卷星模板中！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information, "", null, this);

        }
        private void miExportTestPaperJanDaoYunModel_Click(object sender, RoutedEventArgs e)
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.ShowWarning("当前没有打开任何文档！");
                return;
            }

            var errorMsg = new StringBuilder();
            var questions = QuestionBuilder.BuildQuestions(ae, errorMsg);
            if (questions.Count <= 0)
            {
                LMessageBox.ShowWarning("文档中没有任何可以提取的试题！");
                return;
            }

            StringBuilder sbAll, sbAnswer;
            QuestionBuilder.ExportQuestionsForJianDaoYun(ref questions, out sbAll, out sbAnswer);

            Clipboard.SetData(DataFormats.Text, "参考答案：\t" + sbAnswer + "\n" + sbAll.ToString());
            LMessageBox.Show("请将剪贴板中数据粘贴到空白 Excel 文件中再向简道云网站导入！参考答案是：\r\n\r\n　　" + sbAnswer.ToString(),
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information, "", null, this);

        }

        private void miOutportDocx_Click(object sender, RoutedEventArgs e)
        {
            var errMsg = DocxBuilder.ParseMarkdownToDocx(ActivedEditor);
            if (string.IsNullOrEmpty(errMsg) == false)
            {
                LMessageBox.ShowWarning(errMsg);
            }
        }

        private void miImagesCoverFlow_Click(object sender, RoutedEventArgs e)
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.ShowWarning("没有打开文档！");
                return;
            }

            var reg = new Regex(@"^[ 　]{0,3}!\[.*?\]\(.*?\)", RegexOptions.Multiline);
            MatchCollection matches = reg.Matches(ae.EditorBase.Text);
            if (matches.Count <= 0)
            {
                LMessageBox.ShowWarning("活动编辑器中无单行图像链接！");
                return;
            }

            var imgLinkInfos = new List<ImageLinkInfo>();
            Regex regImageTitle = new Regex(@"(?<=(!\[)).*?(?=(\]\())");
            foreach (Match match in matches)
            {
                var imgLinkInfo = new ImageLinkInfo();
                var m = regImageTitle.Match(match.Value);
                if (m.Success)
                {
                    imgLinkInfo.Title = m.Value;
                }
                imgLinkInfo.FullPath = ae.GetRealPathFromImagelink(match.Value);
                imgLinkInfos.Add(imgLinkInfo);
            }

            var qcDlg = new CoverFlowDialog(ActivedEditor, imgLinkInfos)
            {
                Owner = this,
                WindowStartupLocation = WindowStartupLocation.CenterScreen,
                WindowState = WindowState.Maximized,
            };
            if (qcDlg.ShowDialog() != true) return;
        }

        private void miEditChoiceQuestions_Click(object sender, RoutedEventArgs e)
        {
            var ae = ActivedEditor;
            if (ae == null)
            {
                LMessageBox.ShowWarning("请先打开一个含有选择题的 Markdown 文档！");
                return;
            }

            var reg = new Regex(@"(^　　试题＞＞)((?!　　试题＞＞)[\s\S])*?(^〓{1,6})", RegexOptions.Multiline);
            var matches = reg.Matches(ae.EditorBase.Text);
            if (matches.Count <= 0)
            {
                LMessageBox.ShowWarning("当前文档中没有选择题！");
                return;
            }

            var qilst = new List<QuestionInfo>();
            foreach (Match m in matches)
            {
                var body = m.Value;
                var indexOfAnswer = body.IndexOf("　　答案＞＞");
                if (indexOfAnswer >= 0)
                {
                    var title = body.Substring(0, indexOfAnswer);
                    body = body.Substring(indexOfAnswer);
                    QuestionInfo qi = new QuestionInfo(m, title.Substring(6), body);
                    if (qi.ChoiceItems.Count == 4)  // 确保只有选择题。
                    {
                        qilst.Add(qi);
                    }
                }
            }

            if (qilst.Count > 0)
            {
                QuestionEditor qe = new QuestionEditor(qilst) { Owner = this, };
                if (qe.ShowDialog() == true)
                {
                    ae.EditorBase.BeginChange();

                    var trimChars = new char[] { ' ', '　', '\t', '\r', '\n', };
                    for (int i = qe.QuestionInfos.Count - 1; i >= 0; i--)
                    {
                        var qi = qe.QuestionInfos[i];
                        if (qi.ShouldRemove)
                        {
                            ae.EditorBase.Document.Replace(qi.Match.Index, qi.Match.Length, "");
                            continue;
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.Append("　　试题＞＞");
                        sb.Append(qi.TitleString.Trim(new char[] { '\r', '\n', ' ', '　', '\t', }));
                        sb.Append("\r\n\r\n");

                        if (string.IsNullOrWhiteSpace(qi.MarkdownLinkText) == false)
                        {
                            sb.Append(qi.MarkdownLinkText.Trim(trimChars));
                            sb.Append("\r\n\r\n");
                        }

                        foreach (var ci in qi.ChoiceItems)
                        {
                            if (ci.IsAnswer)
                            {
                                sb.Append("　　答案＞＞");
                                sb.Append(ci.Text.Replace("\r", "").Replace("\n", "").Trim());
                                sb.Append("\r\n");
                                sb.Append("　　解析＞＞");
                                sb.Append(ci.AnalysisText.Replace("\r", "").Replace("\n", "").Trim());
                                sb.Append("\r\n\r\n");
                            }
                        }

                        foreach (var ci in qi.ChoiceItems)
                        {
                            if (ci.IsAnswer == false)
                            {
                                sb.Append("　　错项＞＞");
                                sb.Append(ci.Text.Replace("\r", "").Replace("\n", "").Trim());
                                sb.Append("\r\n");
                                sb.Append("　　解析＞＞");
                                sb.Append(ci.AnalysisText.Replace("\r", "").Replace("\n", "").Trim());
                                sb.Append("\r\n\r\n");
                            }
                        }

                        sb.Append("〓〓〓〓〓〓");
                        ae.EditorBase.Document.Replace(qi.Match.Index, qi.Match.Length, sb.ToString());
                    }
                    ae.EditorBase.EndChange();
                }
            }
        }

        private bool supportFolding = false;

        public bool SupportFolding
        {
            get { return supportFolding; }
        }

        private void miFoldingSwitch_Click(object sender, RoutedEventArgs e)
        {
            supportFolding = miFoldingSwitch.IsChecked = !miFoldingSwitch.IsChecked;

            App.ConfigManager.Set("SupportFolding", supportFolding.ToString());

            foreach (var ue in mainTabControl.Items)
            {
                var me = ue as MarkdownEditor;
                if (me == null || me.EditorBase == null) continue;

                if (supportFolding)
                {
                    me.EditorBase.StartFolding();
                }
                else me.EditorBase.StopFolding();
            }

            if (supportFolding)
            {
                spFolding.Visibility =
                    miFolding.Visibility = Visibility.Visible;
            }
            else
            {
                spFolding.Visibility =
                    miFolding.Visibility = Visibility.Collapsed;
            }
        }

        private void miCopyBranchFilePathes_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.ShowWarning("请先选中要复制路径的分支。");
                return;
            }

            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.ShowWarning("请先选中要复制路径的分支，必须是文件夹或者文件。");
                return;
            }

            switch (selItem.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        Clipboard.SetText(selItem.FullPath, TextDataFormat.Text);
                        LMessageBox.ShowInfomation($"已复制分支【{selItem.Title}】的路径。");
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        var sb = new StringBuilder();
                        GetFilePathesOfBranch(selItem, ref sb);
                        Clipboard.SetText(sb.ToString(), TextDataFormat.Text);
                        LMessageBox.ShowInfomation($"已复制分支【{selItem.Title}】及其下所有文档的路径列表。");
                        break;
                    }
                default:
                    {
                        LMessageBox.ShowInfomation("本操作只支持文件夹或文件。");
                        break;
                    }
            }
        }

        private void miCopySelectedFilePaths_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.Items.Count == 0)
            {
                LMessageBox.ShowWarning("找不到项目可以提取文件路径。");
                return;
            }

            StringBuilder sb = new StringBuilder();
            int count = 0;
            GetSelectedFilePathes(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, ref sb, ref count);
            if (count > 0)
            {
                Clipboard.SetText(sb.ToString(), TextDataFormat.Text);
                LMessageBox.ShowInfomation($"已复制【{count}】条文件路径。");
            }
            else
            {
                LMessageBox.ShowWarning("没有找到可复制的文件路径。");
            }
        }

        public void GetSelectedFilePathes(WorkspaceTreeViewItem wtvi, ref StringBuilder sb, ref int count)
        {
            if (wtvi == null) return;


            switch (wtvi.ItemType)
            {
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        if (wtvi.IsCheckable && wtvi.IsChecked == true)
                        {
                            sb.Append(wtvi.MetaFilePath);
                            sb.Append("\r\n");
                            count++;
                        }

                        foreach (WorkspaceTreeViewItem subWtvi in wtvi.Items)
                        {
                            GetSelectedFilePathes(subWtvi, ref sb, ref count);
                        }
                        break;
                    }
                default:
                    {
                        if (wtvi.IsCheckable && wtvi.IsChecked == true)
                        {
                            sb.Append(wtvi.FullPath);
                            sb.Append("\r\n");
                            count++;
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 此方法会忽略其它类型，只支持 Folder 和 File。
        /// </summary>
        public void GetSelectedMarkdownFileList(WorkspaceTreeViewItem wtvi, ref List<MarkdownFile> mdFileList)
        {
            if (wtvi == null) return;

            switch (wtvi.ItemType)
            {
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        if (wtvi.IsCheckable && wtvi.IsChecked == true)
                        {
                            mdFileList.Add(new MarkdownFile(wtvi.Title, wtvi.MetaFilePath));
                        }

                        foreach (WorkspaceTreeViewItem subWtvi in wtvi.Items)
                        {
                            GetSelectedMarkdownFileList(subWtvi, ref mdFileList);
                        }
                        break;
                    }
                case WorkspaceTreeViewItem.Type.File:
                    {
                        if (wtvi.IsCheckable && wtvi.IsChecked == true)
                        {
                            mdFileList.Add(new MarkdownFile(wtvi.Title, wtvi.FullPath));
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 取指定分支及其下级分支的文件列表。
        /// 只支持文件夹或文件。
        /// </summary>
        public string GetFilePathesOfBranch(WorkspaceTreeViewItem wtvi)
        {
            if (wtvi == null) return null;

            switch (wtvi.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File: return wtvi.FullPath;
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        var sb = new StringBuilder();
                        GetFilePathesOfBranch(wtvi, ref sb);
                        return sb.ToString();
                    }
                default: return null;
            }
        }

        private void GetFilePathesOfBranch(WorkspaceTreeViewItem wtvi, ref StringBuilder sb)
        {
            if (wtvi == null || sb == null) return;

            switch (wtvi.ItemType)
            {
                case WorkspaceTreeViewItem.Type.File:
                    {
                        sb.Append(wtvi.FullPath);
                        sb.Append("\r\n");
                        break;
                    }
                case WorkspaceTreeViewItem.Type.Folder:
                    {
                        sb.Append(wtvi.MetaFilePath);
                        sb.Append("\r\n");
                        foreach (var subItem in wtvi.Items)
                        {
                            GetFilePathesOfBranch(subItem as WorkspaceTreeViewItem, ref sb);
                        }
                        break;
                    }
            }
        }

        private void miCopyQuestionsFromFiles_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.Items.Count <= 0)
            {
                LMessageBox.ShowWarning("工作区管理器中没有任何有效条目！");
                return;
            }

            var rootItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
            if (rootItem == null)
            {
                LMessageBox.ShowWarning("工作区管理器中没有任何有效条目！");
                return;
            }

            var ae = ActivedEditor;
            if (ae == null)
            {
                var result = LMessageBox.Show("没有打开目标文档！这样提取出来的试题中可能包含的图像链接通常是无效的。\r\n\r\n　　真的要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes)
                    return;
            }

            var mdFileList = new List<MarkdownFile>();
            GetSelectedMarkdownFileList(rootItem, ref mdFileList);

            bool containAePath = false;
            string aePath = ae.FullFilePath.ToLower();
            foreach (var mdfile in mdFileList)
            {
                if (aePath == mdfile.FullPath.ToLower())
                {
                    containAePath = true;
                    break;
                }
            }

            if (containAePath)
            {
                LMessageBox.ShowInfomation("搜索结果中会忽略当前活动文档中的试题。\r\n\r\n" +
                    "　　这个功能设计初衷是：从当前工作区中其它选定文件中提取试题到当前文档，再生成 Word 试卷。");
            }

            var qlList = QuestionBuilder.GetQuestionListFromMarkdownFiles(mdFileList, ae == null ? null : ae.FullFilePath);
            //int count = 0;
            if (qlList != null && qlList.Count > 0)
            {
                var filter = new QuestionFilterDialog(qlList) { Owner = this, };
                var result = filter.ShowDialog();
                if (result == true)
                {
                    if (filter.lbxCheckedQuestions.Items.Count <= 0)
                    {
                        LMessageBox.ShowWarning("您并没有在试题筛选器对话框中选取任何试题！");
                    }

                    var sb = new StringBuilder();
                    foreach (var ue in filter.lbxCheckedQuestions.Items)
                    {
                        var card = ue as QuestionCard;
                        if (card == null) continue;
                        // 注意： IsChecked 属性仅用于在查找结果框与选取框间移动，不用于判断是否应插入到当前文档中。 
                        sb.Append(card.MasterQuestion.OutText(true));
                        sb.Append("\r\n\r\n");
                    }
                    if (ae == null)
                    {
                        Clipboard.SetData(DataFormats.Text, sb.ToString());
                        LMessageBox.ShowInfomation("提取的试题已复制到剪贴板！请注意检查图像路径是否准确。");
                        return;
                    }
                    else
                    {
                        ae.EditorBase.SelectedText = sb.ToString();
                    }
                }

                //StringBuilder sb = new StringBuilder();
                //foreach (var qLst in qlList)
                //{
                //    foreach (var q in qLst)
                //    {
                //        if (string.IsNullOrWhiteSpace(q.MarkdownLinkText) == false)
                //        {
                //            q.MarkdownImageLinkRealPath = Utils.ImageLinkTool.GetRealPathFromImagelink(q.MarkdownLinkText, qLst.FilePath);
                //            var relativePath = Utils.ImageLinkTool.GetImageRelativePath(q.MarkdownImageLinkRealPath, ae.FullFilePath);

                //            var imgtip = (string.IsNullOrWhiteSpace(q.MarkdownImageTooltip) ? "" : $" {q.MarkdownImageTooltip}");
                //            q.NewMarkdownLinkText = $"![{q.MarkdownIamgeHeader}]({relativePath}{imgtip})";
                //        }
                //        sb.Append(q.OutText(true));
                //        sb.Append("\r\n\r\n");
                //        count++;
                //    }
                //}
                //Clipboard.SetData(DataFormats.Text, sb.ToString());
                //LMessageBox.ShowInfomation($"已将提取到的【{count}】道试题复制到剪贴板！请粘贴到当前文档中。\r\n\r\n　　注意：如果粘贴到其它文档中，试题可能包含的图像链接是无效的。");
            }
            else
            {
                LMessageBox.Show("没能提取到试题！");
            }
        }

        private bool workspaceCheckable = false;

        public bool WorkspaceCheckable
        {
            get { return workspaceCheckable; }
            set
            {
                workspaceCheckable = value;
                if (tvWorkDirectory.Items.Count > 0)
                {
                    RefreshWorkspaceCheckable(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, value);
                }
            }
        }

        private void RefreshWorkspaceCheckable(WorkspaceTreeViewItem wtvi, bool checkable)
        {
            if (wtvi == null) return;

            wtvi.IsCheckable = checkable;

            if (wtvi.Items.Count > 0)
            {
                foreach (WorkspaceTreeViewItem subItem in wtvi.Items)
                {
                    RefreshWorkspaceCheckable(subItem, checkable);
                }
            }
        }

        private void ckxShowCheckBox_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceCheckable = (ckxShowCheckBox.IsChecked == true);
        }

    }
    public class ImageLinkInfo
    {
        public ImageLinkInfo() { }
        public ImageLinkInfo(string relativePath, string fullPath, string title)
        {
            this.FullPath = fullPath;
            this.Title = title;
        }

        public string FullPath { get; set; }
        public string Title { get; set; }
    }

    /// <summary>
    /// 此类只用于使用树型文字表快速输入分层目录来批量创建工作区子目录。
    /// 树型文字表中每行应由：  Title-Text  （若干空格）  Directory-Short-Name 组成。
    /// 其中，若干空格前的应是 标题文本，空格之后的则是 目录短名。两者本身都不应该包括空格！
    /// </summary>
    class TreeLine
    {
        public string Text
        {
            set
            {
                var splitter = new char[] { ' ', };
                string mark = "";
                var tmp = value.TrimStart(new char[] { ' ', '　', '\t', });
                if (tmp.StartsWith("-") || tmp.StartsWith("－"))
                {
                    mark = "-";
                }

                var txt = value.TrimStart(new char[] { ' ', '　', '\t', '-', '－', });

                var spans = txt.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
                if (spans.Length != 2)
                {
                    title = txt;
                    entryShortName = mark + ChinesePinYin.ToChinesePinYinText(txt);
                }
                else
                {
                    title = spans[0];
                    entryShortName = mark + spans[1];
                }
            }
        }

        private string title = "";
        /// <summary>
        /// 目录标题文本。
        /// </summary>
        public string Title
        {
            get
            {
                if (title.StartsWith("-") || title.StartsWith("－"))
                {
                    title = title.Trim(new char[] { '-', '－', ' ', '　', '\t', });
                }
                return title;
            }
        }

        public string entryShortName = "";
        /// <summary>
        /// 目录短名。
        /// </summary>
        public string EntryShortName
        {
            get
            {
                if (entryShortName.EndsWith("~") || entryShortName.EndsWith("~\\"))
                    return null;
                return entryShortName;
            }
        }

        public int Level { get; set; }
        /// <summary>
        /// 如果创建目录成功，这里应是目录完整路径。
        /// </summary>
        public string CreatedFullPath { get; set; }

        private List<TreeLine> subTreeLines = new List<TreeLine>();
        /// <summary>
        /// 下列树型列表行。
        /// </summary>
        public List<TreeLine> SubTreeLines { get { return subTreeLines; } }

        public TreeLine Parent { get; set; } = null;
    }

    public enum Perspective
    {
        Normal = 0, Default = 0,            // 普通模式
        All = 1,                            // 全部显示
        Editing = 2,                        // 编辑模式
        FullScreenEditing = 3,              // 全屏编辑
        EditingAndPreview = 4,              // 编辑预览
        FullScreenPreview = 5,              // 全屏预览
        EditingAndPresentation = 6,         // 边写边讲
        CompareMode = 7,                    // 对照模式
        MiniMode = 8,                       // 迷你模式
        VerticalMode = 9,                   // 纵向模式
    }

    /// <summary>
    /// 按名称对工作区管理器中的同级条目进行排序。
    /// </summary>
    public class FileSystemEntriesCompare : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return x.ToLower().CompareTo(y.ToLower());
        }
    }
}